* @param {object} opts (see Plotly.toImage in ../plot_api/to_image)
* @return {promise}
*/
function downloadImage(gd, opts) {
var _gd;
if(!Lib.isPlainObject(gd)) _gd = Lib.getGraphDiv(gd);
opts = opts || {};
opts.format = opts.format || 'png';
opts.width = opts.width || null;
opts.height = opts.height || null;
opts.imageDataOnly = true;
return new Promise(function(resolve, reject) {
if(_gd && _gd._snapshotInProgress) {
reject(new Error('Snapshotting already in progress.'));
}
// see comments within svgtoimg for additional
// discussion of problems with IE
// can now draw to canvas, but CORS tainted canvas
// does not allow toDataURL
// svg format will work though
if(Lib.isIE() && opts.format !== 'svg') {
reject(new Error(helpers.MSG_IE_BAD_FORMAT));
}
if(_gd) _gd._snapshotInProgress = true;
var promise = toImage(gd, opts);
var filename = opts.filename || gd.fn || 'newplot';
filename += '.' + opts.format.replace('-', '.');
promise.then(function(result) {
if(_gd) _gd._snapshotInProgress = false;
return fileSaver(result, filename, opts.format);
}).then(function(name) {
resolve(name);
}).catch(function(err) {
if(_gd) _gd._snapshotInProgress = false;
reject(err);
});
});
}
module.exports = downloadImage;
},{"../lib":503,"../plot_api/to_image":546,"./filesaver":641,"./helpers":642}],641:[function(_dereq_,module,exports){
'use strict';
var Lib = _dereq_('../lib');
var helpers = _dereq_('./helpers');
/*
* substantial portions of this code from FileSaver.js
* https://github.com/eligrey/FileSaver.js
* License: https://github.com/eligrey/FileSaver.js/blob/master/LICENSE.md
* FileSaver.js
* A saveAs() FileSaver implementation.
* 1.1.20160328
*
* By Eli Grey, http://eligrey.com
* License: MIT
* See https://github.com/eligrey/FileSaver.js/blob/master/LICENSE.md
*/
function fileSaver(url, name, format) {
var saveLink = document.createElement('a');
var canUseSaveLink = 'download' in saveLink;
var promise = new Promise(function(resolve, reject) {
var blob;
var objectUrl;
// IE 10+ (native saveAs)
if(Lib.isIE()) {
// At this point we are only dealing with a decoded SVG as
// a data URL (since IE only supports SVG)
blob = helpers.createBlob(url, 'svg');
window.navigator.msSaveBlob(blob, name);
blob = null;
return resolve(name);
}
if(canUseSaveLink) {
blob = helpers.createBlob(url, format);
objectUrl = helpers.createObjectURL(blob);
saveLink.href = objectUrl;
saveLink.download = name;
document.body.appendChild(saveLink);
saveLink.click();
document.body.removeChild(saveLink);
helpers.revokeObjectURL(objectUrl);
blob = null;
return resolve(name);
}
// Older versions of Safari did not allow downloading of blob urls
if(Lib.isSafari()) {
var prefix = format === 'svg' ? ',' : ';base64,';
helpers.octetStream(prefix + encodeURIComponent(url));
return resolve(name);
}
reject(new Error('download error'));
});
return promise;
}
module.exports = fileSaver;
},{"../lib":503,"./helpers":642}],642:[function(_dereq_,module,exports){
'use strict';
var Registry = _dereq_('../registry');
exports.getDelay = function(fullLayout) {
if(!fullLayout._has) return 0;
return (
fullLayout._has('gl3d') ||
fullLayout._has('gl2d') ||
fullLayout._has('mapbox')
) ? 500 : 0;
};
exports.getRedrawFunc = function(gd) {
return function() {
Registry.getComponentMethod('colorbar', 'draw')(gd);
};
};
exports.encodeSVG = function(svg) {
return 'data:image/svg+xml,' + encodeURIComponent(svg);
};
exports.encodeJSON = function(json) {
return 'data:application/json,' + encodeURIComponent(json);
};
var DOM_URL = window.URL || window.webkitURL;
exports.createObjectURL = function(blob) {
return DOM_URL.createObjectURL(blob);
};
exports.revokeObjectURL = function(url) {
return DOM_URL.revokeObjectURL(url);
};
exports.createBlob = function(url, format) {
if(format === 'svg') {
return new window.Blob([url], {type: 'image/svg+xml;charset=utf-8'});
} else if(format === 'full-json') {
return new window.Blob([url], {type: 'application/json;charset=utf-8'});
} else {
var binary = fixBinary(window.atob(url));
return new window.Blob([binary], {type: 'image/' + format});
}
};
exports.octetStream = function(s) {
document.location.href = 'data:application/octet-stream' + s;
};
// Taken from https://bl.ocks.org/nolanlawson/0eac306e4dac2114c752
function fixBinary(b) {
var len = b.length;
var buf = new ArrayBuffer(len);
var arr = new Uint8Array(buf);
for(var i = 0; i < len; i++) {
arr[i] = b.charCodeAt(i);
}
return buf;
}
exports.IMAGE_URL_PREFIX = /^data:image\/\w+;base64,/;
exports.MSG_IE_BAD_FORMAT = 'Sorry IE does not support downloading from canvas. Try {format:\'svg\'} instead.';
},{"../registry":638}],643:[function(_dereq_,module,exports){
'use strict';
var helpers = _dereq_('./helpers');
var Snapshot = {
getDelay: helpers.getDelay,
getRedrawFunc: helpers.getRedrawFunc,
clone: _dereq_('./cloneplot'),
toSVG: _dereq_('./tosvg'),
svgToImg: _dereq_('./svgtoimg'),
toImage: _dereq_('./toimage'),
downloadImage: _dereq_('./download')
};
module.exports = Snapshot;
},{"./cloneplot":639,"./download":640,"./helpers":642,"./svgtoimg":644,"./toimage":645,"./tosvg":646}],644:[function(_dereq_,module,exports){
'use strict';
var Lib = _dereq_('../lib');
var EventEmitter = _dereq_('events').EventEmitter;
var helpers = _dereq_('./helpers');
function svgToImg(opts) {
var ev = opts.emitter || new EventEmitter();
var promise = new Promise(function(resolve, reject) {
var Image = window.Image;
var svg = opts.svg;
var format = opts.format || 'png';
// IE only support svg
if(Lib.isIE() && format !== 'svg') {
var ieSvgError = new Error(helpers.MSG_IE_BAD_FORMAT);
reject(ieSvgError);
// eventually remove the ev
// in favor of promises
if(!opts.promise) {
return ev.emit('error', ieSvgError);
} else {
return promise;
}
}
var canvas = opts.canvas;
var scale = opts.scale || 1;
var w0 = opts.width || 300;
var h0 = opts.height || 150;
var w1 = scale * w0;
var h1 = scale * h0;
var ctx = canvas.getContext('2d');
var img = new Image();
var svgBlob, url;
if(format === 'svg' || Lib.isSafari()) {
url = helpers.encodeSVG(svg);
} else {
svgBlob = helpers.createBlob(svg, 'svg');
url = helpers.createObjectURL(svgBlob);
}
canvas.width = w1;
canvas.height = h1;
img.onload = function() {
var imgData;
svgBlob = null;
helpers.revokeObjectURL(url);
// don't need to draw to canvas if svg
// save some time and also avoid failure on IE
if(format !== 'svg') {
ctx.drawImage(img, 0, 0, w1, h1);
}
switch(format) {
case 'jpeg':
imgData = canvas.toDataURL('image/jpeg');
break;
case 'png':
imgData = canvas.toDataURL('image/png');
break;
case 'webp':
imgData = canvas.toDataURL('image/webp');
break;
case 'svg':
imgData = url;
break;
default:
var errorMsg = 'Image format is not jpeg, png, svg or webp.';
reject(new Error(errorMsg));
// eventually remove the ev
// in favor of promises
if(!opts.promise) {
return ev.emit('error', errorMsg);
}
}
resolve(imgData);
// eventually remove the ev
// in favor of promises
if(!opts.promise) {
ev.emit('success', imgData);
}
};
img.onerror = function(err) {
svgBlob = null;
helpers.revokeObjectURL(url);
reject(err);
// eventually remove the ev
// in favor of promises
if(!opts.promise) {
return ev.emit('error', err);
}
};
img.src = url;
});
// temporary for backward compatibility
// move to only Promise in 2.0.0
// and eliminate the EventEmitter
if(opts.promise) {
return promise;
}
return ev;
}
module.exports = svgToImg;
},{"../lib":503,"./helpers":642,"events":84}],645:[function(_dereq_,module,exports){
'use strict';
var EventEmitter = _dereq_('events').EventEmitter;
var Registry = _dereq_('../registry');
var Lib = _dereq_('../lib');
var helpers = _dereq_('./helpers');
var clonePlot = _dereq_('./cloneplot');
var toSVG = _dereq_('./tosvg');
var svgToImg = _dereq_('./svgtoimg');
/**
* @param {object} gd figure Object
* @param {object} opts option object
* @param opts.format 'jpeg' | 'png' | 'webp' | 'svg'
*/
function toImage(gd, opts) {
// first clone the GD so we can operate in a clean environment
var ev = new EventEmitter();
var clone = clonePlot(gd, {format: 'png'});
var clonedGd = clone.gd;
// put the cloned div somewhere off screen before attaching to DOM
clonedGd.style.position = 'absolute';
clonedGd.style.left = '-5000px';
document.body.appendChild(clonedGd);
function wait() {
var delay = helpers.getDelay(clonedGd._fullLayout);
setTimeout(function() {
var svg = toSVG(clonedGd);
var canvas = document.createElement('canvas');
canvas.id = Lib.randstr();
ev = svgToImg({
format: opts.format,
width: clonedGd._fullLayout.width,
height: clonedGd._fullLayout.height,
canvas: canvas,
emitter: ev,
svg: svg
});
ev.clean = function() {
if(clonedGd) document.body.removeChild(clonedGd);
};
}, delay);
}
var redrawFunc = helpers.getRedrawFunc(clonedGd);
Registry.call('_doPlot', clonedGd, clone.data, clone.layout, clone.config)
.then(redrawFunc)
.then(wait)
.catch(function(err) {
ev.emit('error', err);
});
return ev;
}
module.exports = toImage;
},{"../lib":503,"../registry":638,"./cloneplot":639,"./helpers":642,"./svgtoimg":644,"./tosvg":646,"events":84}],646:[function(_dereq_,module,exports){
'use strict';
var d3 = _dereq_('@plotly/d3');
var Lib = _dereq_('../lib');
var Drawing = _dereq_('../components/drawing');
var Color = _dereq_('../components/color');
var xmlnsNamespaces = _dereq_('../constants/xmlns_namespaces');
var DOUBLEQUOTE_REGEX = /"/g;
var DUMMY_SUB = 'TOBESTRIPPED';
var DUMMY_REGEX = new RegExp('("' + DUMMY_SUB + ')|(' + DUMMY_SUB + '")', 'g');
function htmlEntityDecode(s) {
var hiddenDiv = d3.select('body').append('div').style({display: 'none'}).html('');
var replaced = s.replace(/(&[^;]*;)/gi, function(d) {
if(d === '<') { return '<'; } // special handling for brackets
if(d === '&rt;') { return '>'; }
if(d.indexOf('<') !== -1 || d.indexOf('>') !== -1) { return ''; }
return hiddenDiv.html(d).text(); // everything else, let the browser decode it to unicode
});
hiddenDiv.remove();
return replaced;
}
function xmlEntityEncode(str) {
return str.replace(/&(?!\w+;|\#[0-9]+;| \#x[0-9A-F]+;)/g, '&');
}
module.exports = function toSVG(gd, format, scale) {
var fullLayout = gd._fullLayout;
var svg = fullLayout._paper;
var toppaper = fullLayout._toppaper;
var width = fullLayout.width;
var height = fullLayout.height;
var i, k;
// make background color a rect in the svg, then revert after scraping
// all other alterations have been dealt with by properly preparing the svg
// in the first place... like setting cursors with css classes so we don't
// have to remove them, and providing the right namespaces in the svg to
// begin with
svg.insert('rect', ':first-child')
.call(Drawing.setRect, 0, 0, width, height)
.call(Color.fill, fullLayout.paper_bgcolor);
// subplot-specific to-SVG methods
// which notably add the contents of the gl-container
// into the main svg node
var basePlotModules = fullLayout._basePlotModules || [];
for(i = 0; i < basePlotModules.length; i++) {
var _module = basePlotModules[i];
if(_module.toSVG) _module.toSVG(gd);
}
// add top items above them assumes everything in toppaper is either
// a group or a defs, and if it's empty (like hoverlayer) we can ignore it.
if(toppaper) {
var nodes = toppaper.node().childNodes;
// make copy of nodes as childNodes prop gets mutated in loop below
var topGroups = Array.prototype.slice.call(nodes);
for(i = 0; i < topGroups.length; i++) {
var topGroup = topGroups[i];
if(topGroup.childNodes.length) svg.node().appendChild(topGroup);
}
}
// remove draglayer for Adobe Illustrator compatibility
if(fullLayout._draggers) {
fullLayout._draggers.remove();
}
// in case the svg element had an explicit background color, remove this
// we want the rect to get the color so it's the right size; svg bg will
// fill whatever container it's displayed in regardless of plot size.
svg.node().style.background = '';
svg.selectAll('text')
.attr({'data-unformatted': null, 'data-math': null})
.each(function() {
var txt = d3.select(this);
// hidden text is pre-formatting mathjax, the browser ignores it
// but in a static plot it's useless and it can confuse batik
// we've tried to standardize on display:none but make sure we still
// catch visibility:hidden if it ever arises
if(this.style.visibility === 'hidden' || this.style.display === 'none') {
txt.remove();
return;
} else {
// clear other visibility/display values to default
// to not potentially confuse non-browser SVG implementations
txt.style({visibility: null, display: null});
}
// Font family styles break things because of quotation marks,
// so we must remove them *after* the SVG DOM has been serialized
// to a string (browsers convert singles back)
var ff = this.style.fontFamily;
if(ff && ff.indexOf('"') !== -1) {
txt.style('font-family', ff.replace(DOUBLEQUOTE_REGEX, DUMMY_SUB));
}
});
var queryParts = [];
if(fullLayout._gradientUrlQueryParts) {
for(k in fullLayout._gradientUrlQueryParts) queryParts.push(k);
}
if(fullLayout._patternUrlQueryParts) {
for(k in fullLayout._patternUrlQueryParts) queryParts.push(k);
}
if(queryParts.length) {
svg.selectAll(queryParts.join(',')).each(function() {
var pt = d3.select(this);
// similar to font family styles above,
// we must remove " after the SVG DOM has been serialized
var fill = this.style.fill;
if(fill && fill.indexOf('url(') !== -1) {
pt.style('fill', fill.replace(DOUBLEQUOTE_REGEX, DUMMY_SUB));
}
var stroke = this.style.stroke;
if(stroke && stroke.indexOf('url(') !== -1) {
pt.style('stroke', stroke.replace(DOUBLEQUOTE_REGEX, DUMMY_SUB));
}
});
}
if(format === 'pdf' || format === 'eps') {
// these formats make the extra line MathJax adds around symbols look super thick in some cases
// it looks better if this is removed entirely.
svg.selectAll('#MathJax_SVG_glyphs path')
.attr('stroke-width', 0);
}
// fix for IE namespacing quirk?
// http://stackoverflow.com/questions/19610089/unwanted-namespaces-on-svg-markup-when-using-xmlserializer-in-javascript-with-ie
svg.node().setAttributeNS(xmlnsNamespaces.xmlns, 'xmlns', xmlnsNamespaces.svg);
svg.node().setAttributeNS(xmlnsNamespaces.xmlns, 'xmlns:xlink', xmlnsNamespaces.xlink);
if(format === 'svg' && scale) {
svg.attr('width', scale * width);
svg.attr('height', scale * height);
svg.attr('viewBox', '0 0 ' + width + ' ' + height);
}
var s = new window.XMLSerializer().serializeToString(svg.node());
s = htmlEntityDecode(s);
s = xmlEntityEncode(s);
// Fix quotations around font strings and gradient URLs
s = s.replace(DUMMY_REGEX, '\'');
// Do we need this process now that IE9 and IE10 are not supported?
// IE is very strict, so we will need to clean
// svg with the following regex
// yes this is messy, but do not know a better way
// Even with this IE will not work due to tainted canvas
// see https://github.com/kangax/fabric.js/issues/1957
// http://stackoverflow.com/questions/18112047/canvas-todataurl-working-in-all-browsers-except-ie10
// Leave here just in case the CORS/tainted IE issue gets resolved
if(Lib.isIE()) {
// replace double quote with single quote
s = s.replace(/"/gi, '\'');
// url in svg are single quoted
// since we changed double to single
// we'll need to change these to double-quoted
s = s.replace(/(\('#)([^']*)('\))/gi, '(\"#$2\")');
// font names with spaces will be escaped single-quoted
// we'll need to change these to double-quoted
s = s.replace(/(\\')/gi, '\"');
}
return s;
};
},{"../components/color":366,"../components/drawing":388,"../constants/xmlns_namespaces":480,"../lib":503,"@plotly/d3":58}],647:[function(_dereq_,module,exports){
'use strict';
var Lib = _dereq_('../../lib');
// arrayOk attributes, merge them into calcdata array
module.exports = function arraysToCalcdata(cd, trace) {
for(var i = 0; i < cd.length; i++) cd[i].i = i;
Lib.mergeArray(trace.text, cd, 'tx');
Lib.mergeArray(trace.hovertext, cd, 'htx');
var marker = trace.marker;
if(marker) {
Lib.mergeArray(marker.opacity, cd, 'mo', true);
Lib.mergeArray(marker.color, cd, 'mc');
var markerLine = marker.line;
if(markerLine) {
Lib.mergeArray(markerLine.color, cd, 'mlc');
Lib.mergeArrayCastPositive(markerLine.width, cd, 'mlw');
}
}
};
},{"../../lib":503}],648:[function(_dereq_,module,exports){
'use strict';
var scatterAttrs = _dereq_('../scatter/attributes');
var axisHoverFormat = _dereq_('../../plots/cartesian/axis_format_attributes').axisHoverFormat;
var hovertemplateAttrs = _dereq_('../../plots/template_attributes').hovertemplateAttrs;
var texttemplateAttrs = _dereq_('../../plots/template_attributes').texttemplateAttrs;
var colorScaleAttrs = _dereq_('../../components/colorscale/attributes');
var fontAttrs = _dereq_('../../plots/font_attributes');
var constants = _dereq_('./constants');
var pattern = _dereq_('../../components/drawing/attributes').pattern;
var extendFlat = _dereq_('../../lib/extend').extendFlat;
var textFontAttrs = fontAttrs({
editType: 'calc',
arrayOk: true,
colorEditType: 'style',
});
var scatterMarkerAttrs = scatterAttrs.marker;
var scatterMarkerLineAttrs = scatterMarkerAttrs.line;
var markerLineWidth = extendFlat({},
scatterMarkerLineAttrs.width, { dflt: 0 });
var markerLine = extendFlat({
width: markerLineWidth,
editType: 'calc'
}, colorScaleAttrs('marker.line'));
var marker = extendFlat({
line: markerLine,
editType: 'calc'
}, colorScaleAttrs('marker'), {
opacity: {
valType: 'number',
arrayOk: true,
dflt: 1,
min: 0,
max: 1,
editType: 'style',
},
pattern: pattern
});
module.exports = {
x: scatterAttrs.x,
x0: scatterAttrs.x0,
dx: scatterAttrs.dx,
y: scatterAttrs.y,
y0: scatterAttrs.y0,
dy: scatterAttrs.dy,
xperiod: scatterAttrs.xperiod,
yperiod: scatterAttrs.yperiod,
xperiod0: scatterAttrs.xperiod0,
yperiod0: scatterAttrs.yperiod0,
xperiodalignment: scatterAttrs.xperiodalignment,
yperiodalignment: scatterAttrs.yperiodalignment,
xhoverformat: axisHoverFormat('x'),
yhoverformat: axisHoverFormat('y'),
text: scatterAttrs.text,
texttemplate: texttemplateAttrs({editType: 'plot'}, {
keys: constants.eventDataKeys
}),
hovertext: scatterAttrs.hovertext,
hovertemplate: hovertemplateAttrs({}, {
keys: constants.eventDataKeys
}),
textposition: {
valType: 'enumerated',
values: ['inside', 'outside', 'auto', 'none'],
dflt: 'auto',
arrayOk: true,
editType: 'calc',
},
insidetextanchor: {
valType: 'enumerated',
values: ['end', 'middle', 'start'],
dflt: 'end',
editType: 'plot',
},
textangle: {
valType: 'angle',
dflt: 'auto',
editType: 'plot',
},
textfont: extendFlat({}, textFontAttrs, {
}),
insidetextfont: extendFlat({}, textFontAttrs, {
}),
outsidetextfont: extendFlat({}, textFontAttrs, {
}),
constraintext: {
valType: 'enumerated',
values: ['inside', 'outside', 'both', 'none'],
dflt: 'both',
editType: 'calc',
},
cliponaxis: extendFlat({}, scatterAttrs.cliponaxis, {
}),
orientation: {
valType: 'enumerated',
values: ['v', 'h'],
editType: 'calc+clearAxisTypes',
},
base: {
valType: 'any',
dflt: null,
arrayOk: true,
editType: 'calc',
},
offset: {
valType: 'number',
dflt: null,
arrayOk: true,
editType: 'calc',
},
width: {
valType: 'number',
dflt: null,
min: 0,
arrayOk: true,
editType: 'calc',
},
marker: marker,
offsetgroup: {
valType: 'string',
dflt: '',
editType: 'calc',
},
alignmentgroup: {
valType: 'string',
dflt: '',
editType: 'calc',
},
selected: {
marker: {
opacity: scatterAttrs.selected.marker.opacity,
color: scatterAttrs.selected.marker.color,
editType: 'style'
},
textfont: scatterAttrs.selected.textfont,
editType: 'style'
},
unselected: {
marker: {
opacity: scatterAttrs.unselected.marker.opacity,
color: scatterAttrs.unselected.marker.color,
editType: 'style'
},
textfont: scatterAttrs.unselected.textfont,
editType: 'style'
},
_deprecated: {
bardir: {
valType: 'enumerated',
editType: 'calc',
values: ['v', 'h'],
}
}
};
},{"../../components/colorscale/attributes":373,"../../components/drawing/attributes":387,"../../lib/extend":493,"../../plots/cartesian/axis_format_attributes":557,"../../plots/font_attributes":585,"../../plots/template_attributes":633,"../scatter/attributes":925,"./constants":650}],649:[function(_dereq_,module,exports){
'use strict';
var Axes = _dereq_('../../plots/cartesian/axes');
var alignPeriod = _dereq_('../../plots/cartesian/align_period');
var hasColorscale = _dereq_('../../components/colorscale/helpers').hasColorscale;
var colorscaleCalc = _dereq_('../../components/colorscale/calc');
var arraysToCalcdata = _dereq_('./arrays_to_calcdata');
var calcSelection = _dereq_('../scatter/calc_selection');
module.exports = function calc(gd, trace) {
var xa = Axes.getFromId(gd, trace.xaxis || 'x');
var ya = Axes.getFromId(gd, trace.yaxis || 'y');
var size, pos, origPos, pObj, hasPeriod, pLetter;
var sizeOpts = {
msUTC: !!(trace.base || trace.base === 0)
};
if(trace.orientation === 'h') {
size = xa.makeCalcdata(trace, 'x', sizeOpts);
origPos = ya.makeCalcdata(trace, 'y');
pObj = alignPeriod(trace, ya, 'y', origPos);
hasPeriod = !!trace.yperiodalignment;
pLetter = 'y';
} else {
size = ya.makeCalcdata(trace, 'y', sizeOpts);
origPos = xa.makeCalcdata(trace, 'x');
pObj = alignPeriod(trace, xa, 'x', origPos);
hasPeriod = !!trace.xperiodalignment;
pLetter = 'x';
}
pos = pObj.vals;
// create the "calculated data" to plot
var serieslen = Math.min(pos.length, size.length);
var cd = new Array(serieslen);
// set position and size
for(var i = 0; i < serieslen; i++) {
cd[i] = { p: pos[i], s: size[i] };
if(hasPeriod) {
cd[i].orig_p = origPos[i]; // used by hover
cd[i][pLetter + 'End'] = pObj.ends[i];
cd[i][pLetter + 'Start'] = pObj.starts[i];
}
if(trace.ids) {
cd[i].id = String(trace.ids[i]);
}
}
// auto-z and autocolorscale if applicable
if(hasColorscale(trace, 'marker')) {
colorscaleCalc(gd, trace, {
vals: trace.marker.color,
containerStr: 'marker',
cLetter: 'c'
});
}
if(hasColorscale(trace, 'marker.line')) {
colorscaleCalc(gd, trace, {
vals: trace.marker.line.color,
containerStr: 'marker.line',
cLetter: 'c'
});
}
arraysToCalcdata(cd, trace);
calcSelection(cd, trace);
return cd;
};
},{"../../components/colorscale/calc":374,"../../components/colorscale/helpers":377,"../../plots/cartesian/align_period":551,"../../plots/cartesian/axes":554,"../scatter/calc_selection":927,"./arrays_to_calcdata":647}],650:[function(_dereq_,module,exports){
'use strict';
module.exports = {
// padding in pixels around text
TEXTPAD: 3,
// 'value' and 'label' are not really necessary for bar traces,
// but they were made available to `texttemplate` (maybe by accident)
// via tokens `%{value}` and `%{label}` starting in 1.50.0,
// so let's include them in the event data also.
eventDataKeys: ['value', 'label']
};
},{}],651:[function(_dereq_,module,exports){
'use strict';
var isNumeric = _dereq_('fast-isnumeric');
var isArrayOrTypedArray = _dereq_('../../lib').isArrayOrTypedArray;
var BADNUM = _dereq_('../../constants/numerical').BADNUM;
var Registry = _dereq_('../../registry');
var Axes = _dereq_('../../plots/cartesian/axes');
var getAxisGroup = _dereq_('../../plots/cartesian/constraints').getAxisGroup;
var Sieve = _dereq_('./sieve.js');
/*
* Bar chart stacking/grouping positioning and autoscaling calculations
* for each direction separately calculate the ranges and positions
* note that this handles histograms too
* now doing this one subplot at a time
*/
function crossTraceCalc(gd, plotinfo) {
var xa = plotinfo.xaxis;
var ya = plotinfo.yaxis;
var fullLayout = gd._fullLayout;
var fullTraces = gd._fullData;
var calcTraces = gd.calcdata;
var calcTracesHorz = [];
var calcTracesVert = [];
for(var i = 0; i < fullTraces.length; i++) {
var fullTrace = fullTraces[i];
if(
fullTrace.visible === true &&
Registry.traceIs(fullTrace, 'bar') &&
fullTrace.xaxis === xa._id &&
fullTrace.yaxis === ya._id
) {
if(fullTrace.orientation === 'h') {
calcTracesHorz.push(calcTraces[i]);
} else {
calcTracesVert.push(calcTraces[i]);
}
if(fullTrace._computePh) {
var cd = gd.calcdata[i];
for(var j = 0; j < cd.length; j++) {
if(typeof cd[j].ph0 === 'function') cd[j].ph0 = cd[j].ph0();
if(typeof cd[j].ph1 === 'function') cd[j].ph1 = cd[j].ph1();
}
}
}
}
var opts = {
xCat: xa.type === 'category' || xa.type === 'multicategory',
yCat: ya.type === 'category' || ya.type === 'multicategory',
mode: fullLayout.barmode,
norm: fullLayout.barnorm,
gap: fullLayout.bargap,
groupgap: fullLayout.bargroupgap
};
setGroupPositions(gd, xa, ya, calcTracesVert, opts);
setGroupPositions(gd, ya, xa, calcTracesHorz, opts);
}
function setGroupPositions(gd, pa, sa, calcTraces, opts) {
if(!calcTraces.length) return;
var excluded;
var included;
var i, calcTrace, fullTrace;
initBase(sa, calcTraces);
switch(opts.mode) {
case 'overlay':
setGroupPositionsInOverlayMode(pa, sa, calcTraces, opts);
break;
case 'group':
// exclude from the group those traces for which the user set an offset
excluded = [];
included = [];
for(i = 0; i < calcTraces.length; i++) {
calcTrace = calcTraces[i];
fullTrace = calcTrace[0].trace;
if(fullTrace.offset === undefined) included.push(calcTrace);
else excluded.push(calcTrace);
}
if(included.length) {
setGroupPositionsInGroupMode(gd, pa, sa, included, opts);
}
if(excluded.length) {
setGroupPositionsInOverlayMode(pa, sa, excluded, opts);
}
break;
case 'stack':
case 'relative':
// exclude from the stack those traces for which the user set a base
excluded = [];
included = [];
for(i = 0; i < calcTraces.length; i++) {
calcTrace = calcTraces[i];
fullTrace = calcTrace[0].trace;
if(fullTrace.base === undefined) included.push(calcTrace);
else excluded.push(calcTrace);
}
if(included.length) {
setGroupPositionsInStackOrRelativeMode(gd, pa, sa, included, opts);
}
if(excluded.length) {
setGroupPositionsInOverlayMode(pa, sa, excluded, opts);
}
break;
}
collectExtents(calcTraces, pa);
}
function initBase(sa, calcTraces) {
var i, j;
for(i = 0; i < calcTraces.length; i++) {
var cd = calcTraces[i];
var trace = cd[0].trace;
var base = (trace.type === 'funnel') ? trace._base : trace.base;
var b;
// not sure if it really makes sense to have dates for bar size data...
// ideally if we want to make gantt charts or something we'd treat
// the actual size (trace.x or y) as time delta but base as absolute
// time. But included here for completeness.
var scalendar = trace.orientation === 'h' ? trace.xcalendar : trace.ycalendar;
// 'base' on categorical axes makes no sense
var d2c = sa.type === 'category' || sa.type === 'multicategory' ?
function() { return null; } :
sa.d2c;
if(isArrayOrTypedArray(base)) {
for(j = 0; j < Math.min(base.length, cd.length); j++) {
b = d2c(base[j], 0, scalendar);
if(isNumeric(b)) {
cd[j].b = +b;
cd[j].hasB = 1;
} else cd[j].b = 0;
}
for(; j < cd.length; j++) {
cd[j].b = 0;
}
} else {
b = d2c(base, 0, scalendar);
var hasBase = isNumeric(b);
b = hasBase ? b : 0;
for(j = 0; j < cd.length; j++) {
cd[j].b = b;
if(hasBase) cd[j].hasB = 1;
}
}
}
}
function setGroupPositionsInOverlayMode(pa, sa, calcTraces, opts) {
// update position axis and set bar offsets and widths
for(var i = 0; i < calcTraces.length; i++) {
var calcTrace = calcTraces[i];
var sieve = new Sieve([calcTrace], {
posAxis: pa,
sepNegVal: false,
overlapNoMerge: !opts.norm
});
// set bar offsets and widths, and update position axis
setOffsetAndWidth(pa, sieve, opts);
// set bar bases and sizes, and update size axis
//
// (note that `setGroupPositionsInOverlayMode` handles the case barnorm
// is defined, because this function is also invoked for traces that
// can't be grouped or stacked)
if(opts.norm) {
sieveBars(sieve);
normalizeBars(sa, sieve, opts);
} else {
setBaseAndTop(sa, sieve);
}
}
}
function setGroupPositionsInGroupMode(gd, pa, sa, calcTraces, opts) {
var sieve = new Sieve(calcTraces, {
posAxis: pa,
sepNegVal: false,
overlapNoMerge: !opts.norm
});
// set bar offsets and widths, and update position axis
setOffsetAndWidthInGroupMode(gd, pa, sieve, opts);
// relative-stack bars within the same trace that would otherwise
// be hidden
unhideBarsWithinTrace(sieve, pa);
// set bar bases and sizes, and update size axis
if(opts.norm) {
sieveBars(sieve);
normalizeBars(sa, sieve, opts);
} else {
setBaseAndTop(sa, sieve);
}
}
function setGroupPositionsInStackOrRelativeMode(gd, pa, sa, calcTraces, opts) {
var sieve = new Sieve(calcTraces, {
posAxis: pa,
sepNegVal: opts.mode === 'relative',
overlapNoMerge: !(opts.norm || opts.mode === 'stack' || opts.mode === 'relative')
});
// set bar offsets and widths, and update position axis
setOffsetAndWidth(pa, sieve, opts);
// set bar bases and sizes, and update size axis
stackBars(sa, sieve, opts);
// flag the outmost bar (for text display purposes)
for(var i = 0; i < calcTraces.length; i++) {
var calcTrace = calcTraces[i];
for(var j = 0; j < calcTrace.length; j++) {
var bar = calcTrace[j];
if(bar.s !== BADNUM) {
var isOutmostBar = ((bar.b + bar.s) === sieve.get(bar.p, bar.s));
if(isOutmostBar) bar._outmost = true;
}
}
}
// Note that marking the outmost bars has to be done
// before `normalizeBars` changes `bar.b` and `bar.s`.
if(opts.norm) normalizeBars(sa, sieve, opts);
}
function setOffsetAndWidth(pa, sieve, opts) {
var minDiff = sieve.minDiff;
var calcTraces = sieve.traces;
// set bar offsets and widths
var barGroupWidth = minDiff * (1 - opts.gap);
var barWidthPlusGap = barGroupWidth;
var barWidth = barWidthPlusGap * (1 - (opts.groupgap || 0));
// computer bar group center and bar offset
var offsetFromCenter = -barWidth / 2;
for(var i = 0; i < calcTraces.length; i++) {
var calcTrace = calcTraces[i];
var t = calcTrace[0].t;
// store bar width and offset for this trace
t.barwidth = barWidth;
t.poffset = offsetFromCenter;
t.bargroupwidth = barGroupWidth;
t.bardelta = minDiff;
}
// stack bars that only differ by rounding
sieve.binWidth = calcTraces[0][0].t.barwidth / 100;
// if defined, apply trace offset and width
applyAttributes(sieve);
// store the bar center in each calcdata item
setBarCenterAndWidth(pa, sieve);
// update position axes
updatePositionAxis(pa, sieve);
}
function setOffsetAndWidthInGroupMode(gd, pa, sieve, opts) {
var fullLayout = gd._fullLayout;
var positions = sieve.positions;
var distinctPositions = sieve.distinctPositions;
var minDiff = sieve.minDiff;
var calcTraces = sieve.traces;
var nTraces = calcTraces.length;
// if there aren't any overlapping positions,
// let them have full width even if mode is group
var overlap = (positions.length !== distinctPositions.length);
var barGroupWidth = minDiff * (1 - opts.gap);
var groupId = getAxisGroup(fullLayout, pa._id) + calcTraces[0][0].trace.orientation;
var alignmentGroups = fullLayout._alignmentOpts[groupId] || {};
for(var i = 0; i < nTraces; i++) {
var calcTrace = calcTraces[i];
var trace = calcTrace[0].trace;
var alignmentGroupOpts = alignmentGroups[trace.alignmentgroup] || {};
var nOffsetGroups = Object.keys(alignmentGroupOpts.offsetGroups || {}).length;
var barWidthPlusGap;
if(nOffsetGroups) {
barWidthPlusGap = barGroupWidth / nOffsetGroups;
} else {
barWidthPlusGap = overlap ? barGroupWidth / nTraces : barGroupWidth;
}
var barWidth = barWidthPlusGap * (1 - (opts.groupgap || 0));
var offsetFromCenter;
if(nOffsetGroups) {
offsetFromCenter = ((2 * trace._offsetIndex + 1 - nOffsetGroups) * barWidthPlusGap - barWidth) / 2;
} else {
offsetFromCenter = overlap ?
((2 * i + 1 - nTraces) * barWidthPlusGap - barWidth) / 2 :
-barWidth / 2;
}
var t = calcTrace[0].t;
t.barwidth = barWidth;
t.poffset = offsetFromCenter;
t.bargroupwidth = barGroupWidth;
t.bardelta = minDiff;
}
// stack bars that only differ by rounding
sieve.binWidth = calcTraces[0][0].t.barwidth / 100;
// if defined, apply trace width
applyAttributes(sieve);
// store the bar center in each calcdata item
setBarCenterAndWidth(pa, sieve);
// update position axes
updatePositionAxis(pa, sieve, overlap);
}
function applyAttributes(sieve) {
var calcTraces = sieve.traces;
var i, j;
for(i = 0; i < calcTraces.length; i++) {
var calcTrace = calcTraces[i];
var calcTrace0 = calcTrace[0];
var fullTrace = calcTrace0.trace;
var t = calcTrace0.t;
var offset = fullTrace._offset || fullTrace.offset;
var initialPoffset = t.poffset;
var newPoffset;
if(isArrayOrTypedArray(offset)) {
// if offset is an array, then clone it into t.poffset.
newPoffset = Array.prototype.slice.call(offset, 0, calcTrace.length);
// guard against non-numeric items
for(j = 0; j < newPoffset.length; j++) {
if(!isNumeric(newPoffset[j])) {
newPoffset[j] = initialPoffset;
}
}
// if the length of the array is too short,
// then extend it with the initial value of t.poffset
for(j = newPoffset.length; j < calcTrace.length; j++) {
newPoffset.push(initialPoffset);
}
t.poffset = newPoffset;
} else if(offset !== undefined) {
t.poffset = offset;
}
var width = fullTrace._width || fullTrace.width;
var initialBarwidth = t.barwidth;
if(isArrayOrTypedArray(width)) {
// if width is an array, then clone it into t.barwidth.
var newBarwidth = Array.prototype.slice.call(width, 0, calcTrace.length);
// guard against non-numeric items
for(j = 0; j < newBarwidth.length; j++) {
if(!isNumeric(newBarwidth[j])) newBarwidth[j] = initialBarwidth;
}
// if the length of the array is too short,
// then extend it with the initial value of t.barwidth
for(j = newBarwidth.length; j < calcTrace.length; j++) {
newBarwidth.push(initialBarwidth);
}
t.barwidth = newBarwidth;
// if user didn't set offset,
// then correct t.poffset to ensure bars remain centered
if(offset === undefined) {
newPoffset = [];
for(j = 0; j < calcTrace.length; j++) {
newPoffset.push(
initialPoffset + (initialBarwidth - newBarwidth[j]) / 2
);
}
t.poffset = newPoffset;
}
} else if(width !== undefined) {
t.barwidth = width;
// if user didn't set offset,
// then correct t.poffset to ensure bars remain centered
if(offset === undefined) {
t.poffset = initialPoffset + (initialBarwidth - width) / 2;
}
}
}
}
function setBarCenterAndWidth(pa, sieve) {
var calcTraces = sieve.traces;
var pLetter = getAxisLetter(pa);
for(var i = 0; i < calcTraces.length; i++) {
var calcTrace = calcTraces[i];
var t = calcTrace[0].t;
var poffset = t.poffset;
var poffsetIsArray = Array.isArray(poffset);
var barwidth = t.barwidth;
var barwidthIsArray = Array.isArray(barwidth);
for(var j = 0; j < calcTrace.length; j++) {
var calcBar = calcTrace[j];
// store the actual bar width and position, for use by hover
var width = calcBar.w = barwidthIsArray ? barwidth[j] : barwidth;
calcBar[pLetter] = calcBar.p + (poffsetIsArray ? poffset[j] : poffset) + width / 2;
}
}
}
function updatePositionAxis(pa, sieve, allowMinDtick) {
var calcTraces = sieve.traces;
var minDiff = sieve.minDiff;
var vpad = minDiff / 2;
Axes.minDtick(pa, sieve.minDiff, sieve.distinctPositions[0], allowMinDtick);
for(var i = 0; i < calcTraces.length; i++) {
var calcTrace = calcTraces[i];
var calcTrace0 = calcTrace[0];
var fullTrace = calcTrace0.trace;
var pts = [];
var bar, l, r, j;
for(j = 0; j < calcTrace.length; j++) {
bar = calcTrace[j];
l = bar.p - vpad;
r = bar.p + vpad;
pts.push(l, r);
}
if(fullTrace.width || fullTrace.offset) {
var t = calcTrace0.t;
var poffset = t.poffset;
var barwidth = t.barwidth;
var poffsetIsArray = Array.isArray(poffset);
var barwidthIsArray = Array.isArray(barwidth);
for(j = 0; j < calcTrace.length; j++) {
bar = calcTrace[j];
var calcBarOffset = poffsetIsArray ? poffset[j] : poffset;
var calcBarWidth = barwidthIsArray ? barwidth[j] : barwidth;
l = bar.p + calcBarOffset;
r = l + calcBarWidth;
pts.push(l, r);
}
}
fullTrace._extremes[pa._id] = Axes.findExtremes(pa, pts, {padded: false});
}
}
// store these bar bases and tops in calcdata
// and make sure the size axis includes zero,
// along with the bases and tops of each bar.
function setBaseAndTop(sa, sieve) {
var calcTraces = sieve.traces;
var sLetter = getAxisLetter(sa);
for(var i = 0; i < calcTraces.length; i++) {
var calcTrace = calcTraces[i];
var fullTrace = calcTrace[0].trace;
var pts = [];
var tozero = false;
for(var j = 0; j < calcTrace.length; j++) {
var bar = calcTrace[j];
var base = bar.b;
var top = base + bar.s;
bar[sLetter] = top;
pts.push(top);
if(bar.hasB) pts.push(base);
if(!bar.hasB || !bar.b) {
tozero = true;
}
}
fullTrace._extremes[sa._id] = Axes.findExtremes(sa, pts, {
tozero: tozero,
padded: true
});
}
}
function stackBars(sa, sieve, opts) {
var sLetter = getAxisLetter(sa);
var calcTraces = sieve.traces;
var calcTrace;
var fullTrace;
var isFunnel;
var i, j;
var bar;
for(i = 0; i < calcTraces.length; i++) {
calcTrace = calcTraces[i];
fullTrace = calcTrace[0].trace;
if(fullTrace.type === 'funnel') {
for(j = 0; j < calcTrace.length; j++) {
bar = calcTrace[j];
if(bar.s !== BADNUM) {
// create base of funnels
sieve.put(bar.p, -0.5 * bar.s);
}
}
}
}
for(i = 0; i < calcTraces.length; i++) {
calcTrace = calcTraces[i];
fullTrace = calcTrace[0].trace;
isFunnel = (fullTrace.type === 'funnel');
var pts = [];
for(j = 0; j < calcTrace.length; j++) {
bar = calcTrace[j];
if(bar.s !== BADNUM) {
// stack current bar and get previous sum
var value;
if(isFunnel) {
value = bar.s;
} else {
value = bar.s + bar.b;
}
var base = sieve.put(bar.p, value);
var top = base + value;
// store the bar base and top in each calcdata item
bar.b = base;
bar[sLetter] = top;
if(!opts.norm) {
pts.push(top);
if(bar.hasB) {
pts.push(base);
}
}
}
}
// if barnorm is set, let normalizeBars update the axis range
if(!opts.norm) {
fullTrace._extremes[sa._id] = Axes.findExtremes(sa, pts, {
// N.B. we don't stack base with 'base',
// so set tozero:true always!
tozero: true,
padded: true
});
}
}
}
function sieveBars(sieve) {
var calcTraces = sieve.traces;
for(var i = 0; i < calcTraces.length; i++) {
var calcTrace = calcTraces[i];
for(var j = 0; j < calcTrace.length; j++) {
var bar = calcTrace[j];
if(bar.s !== BADNUM) {
sieve.put(bar.p, bar.b + bar.s);
}
}
}
}
function unhideBarsWithinTrace(sieve, pa) {
var calcTraces = sieve.traces;
for(var i = 0; i < calcTraces.length; i++) {
var calcTrace = calcTraces[i];
var fullTrace = calcTrace[0].trace;
if(fullTrace.base === undefined) {
var inTraceSieve = new Sieve([calcTrace], {
posAxis: pa,
sepNegVal: true,
overlapNoMerge: true
});
for(var j = 0; j < calcTrace.length; j++) {
var bar = calcTrace[j];
if(bar.p !== BADNUM) {
// stack current bar and get previous sum
var base = inTraceSieve.put(bar.p, bar.b + bar.s);
// if previous sum if non-zero, this means:
// multiple bars have same starting point are potentially hidden,
// shift them vertically so that all bars are visible by default
if(base) bar.b = base;
}
}
}
}
}
// Note:
//
// normalizeBars requires that either sieveBars or stackBars has been
// previously invoked.
function normalizeBars(sa, sieve, opts) {
var calcTraces = sieve.traces;
var sLetter = getAxisLetter(sa);
var sTop = opts.norm === 'fraction' ? 1 : 100;
var sTiny = sTop / 1e9; // in case of rounding error in sum
var sMin = sa.l2c(sa.c2l(0));
var sMax = opts.mode === 'stack' ? sTop : sMin;
function needsPadding(v) {
return (
isNumeric(sa.c2l(v)) &&
((v < sMin - sTiny) || (v > sMax + sTiny) || !isNumeric(sMin))
);
}
for(var i = 0; i < calcTraces.length; i++) {
var calcTrace = calcTraces[i];
var fullTrace = calcTrace[0].trace;
var pts = [];
var tozero = false;
var padded = false;
for(var j = 0; j < calcTrace.length; j++) {
var bar = calcTrace[j];
if(bar.s !== BADNUM) {
var scale = Math.abs(sTop / sieve.get(bar.p, bar.s));
bar.b *= scale;
bar.s *= scale;
var base = bar.b;
var top = base + bar.s;
bar[sLetter] = top;
pts.push(top);
padded = padded || needsPadding(top);
if(bar.hasB) {
pts.push(base);
padded = padded || needsPadding(base);
}
if(!bar.hasB || !bar.b) {
tozero = true;
}
}
}
fullTrace._extremes[sa._id] = Axes.findExtremes(sa, pts, {
tozero: tozero,
padded: padded
});
}
}
// find the full position span of bars at each position
// for use by hover, to ensure labels move in if bars are
// narrower than the space they're in.
// run once per trace group (subplot & direction) and
// the same mapping is attached to all calcdata traces
function collectExtents(calcTraces, pa) {
var pLetter = getAxisLetter(pa);
var extents = {};
var i, j, cd;
var pMin = Infinity;
var pMax = -Infinity;
for(i = 0; i < calcTraces.length; i++) {
cd = calcTraces[i];
for(j = 0; j < cd.length; j++) {
var p = cd[j].p;
if(isNumeric(p)) {
pMin = Math.min(pMin, p);
pMax = Math.max(pMax, p);
}
}
}
// this is just for positioning of hover labels, and nobody will care if
// the label is 1px too far out; so round positions to 1/10K in case
// position values don't exactly match from trace to trace
var roundFactor = 10000 / (pMax - pMin);
var round = extents.round = function(p) {
return String(Math.round(roundFactor * (p - pMin)));
};
for(i = 0; i < calcTraces.length; i++) {
cd = calcTraces[i];
cd[0].t.extents = extents;
var poffset = cd[0].t.poffset;
var poffsetIsArray = Array.isArray(poffset);
for(j = 0; j < cd.length; j++) {
var di = cd[j];
var p0 = di[pLetter] - di.w / 2;
if(isNumeric(p0)) {
var p1 = di[pLetter] + di.w / 2;
var pVal = round(di.p);
if(extents[pVal]) {
extents[pVal] = [Math.min(p0, extents[pVal][0]), Math.max(p1, extents[pVal][1])];
} else {
extents[pVal] = [p0, p1];
}
}
di.p0 = di.p + (poffsetIsArray ? poffset[j] : poffset);
di.p1 = di.p0 + di.w;
di.s0 = di.b;
di.s1 = di.s0 + di.s;
}
}
}
function getAxisLetter(ax) {
return ax._id.charAt(0);
}
module.exports = {
crossTraceCalc: crossTraceCalc,
setGroupPositions: setGroupPositions
};
},{"../../constants/numerical":479,"../../lib":503,"../../plots/cartesian/axes":554,"../../plots/cartesian/constraints":562,"../../registry":638,"./sieve.js":661,"fast-isnumeric":190}],652:[function(_dereq_,module,exports){
'use strict';
var Lib = _dereq_('../../lib');
var Color = _dereq_('../../components/color');
var Registry = _dereq_('../../registry');
var handleXYDefaults = _dereq_('../scatter/xy_defaults');
var handlePeriodDefaults = _dereq_('../scatter/period_defaults');
var handleStyleDefaults = _dereq_('./style_defaults');
var getAxisGroup = _dereq_('../../plots/cartesian/constraints').getAxisGroup;
var attributes = _dereq_('./attributes');
var coerceFont = Lib.coerceFont;
function supplyDefaults(traceIn, traceOut, defaultColor, layout) {
function coerce(attr, dflt) {
return Lib.coerce(traceIn, traceOut, attributes, attr, dflt);
}
var len = handleXYDefaults(traceIn, traceOut, layout, coerce);
if(!len) {
traceOut.visible = false;
return;
}
handlePeriodDefaults(traceIn, traceOut, layout, coerce);
coerce('xhoverformat');
coerce('yhoverformat');
coerce('orientation', (traceOut.x && !traceOut.y) ? 'h' : 'v');
coerce('base');
coerce('offset');
coerce('width');
coerce('text');
coerce('hovertext');
coerce('hovertemplate');
var textposition = coerce('textposition');
handleText(traceIn, traceOut, layout, coerce, textposition, {
moduleHasSelected: true,
moduleHasUnselected: true,
moduleHasConstrain: true,
moduleHasCliponaxis: true,
moduleHasTextangle: true,
moduleHasInsideanchor: true
});
handleStyleDefaults(traceIn, traceOut, coerce, defaultColor, layout);
var lineColor = (traceOut.marker.line || {}).color;
// override defaultColor for error bars with defaultLine
var errorBarsSupplyDefaults = Registry.getComponentMethod('errorbars', 'supplyDefaults');
errorBarsSupplyDefaults(traceIn, traceOut, lineColor || Color.defaultLine, {axis: 'y'});
errorBarsSupplyDefaults(traceIn, traceOut, lineColor || Color.defaultLine, {axis: 'x', inherit: 'y'});
Lib.coerceSelectionMarkerOpacity(traceOut, coerce);
}
function handleGroupingDefaults(traceIn, traceOut, fullLayout, coerce) {
var orientation = traceOut.orientation;
// N.B. grouping is done across all trace types that support it
var posAxId = traceOut[{v: 'x', h: 'y'}[orientation] + 'axis'];
var groupId = getAxisGroup(fullLayout, posAxId) + orientation;
var alignmentOpts = fullLayout._alignmentOpts || {};
var alignmentgroup = coerce('alignmentgroup');
var alignmentGroups = alignmentOpts[groupId];
if(!alignmentGroups) alignmentGroups = alignmentOpts[groupId] = {};
var alignmentGroupOpts = alignmentGroups[alignmentgroup];
if(alignmentGroupOpts) {
alignmentGroupOpts.traces.push(traceOut);
} else {
alignmentGroupOpts = alignmentGroups[alignmentgroup] = {
traces: [traceOut],
alignmentIndex: Object.keys(alignmentGroups).length,
offsetGroups: {}
};
}
var offsetgroup = coerce('offsetgroup');
var offsetGroups = alignmentGroupOpts.offsetGroups;
var offsetGroupOpts = offsetGroups[offsetgroup];
if(offsetgroup) {
if(!offsetGroupOpts) {
offsetGroupOpts = offsetGroups[offsetgroup] = {
offsetIndex: Object.keys(offsetGroups).length
};
}
traceOut._offsetIndex = offsetGroupOpts.offsetIndex;
}
}
function crossTraceDefaults(fullData, fullLayout) {
var traceIn, traceOut;
function coerce(attr) {
return Lib.coerce(traceOut._input, traceOut, attributes, attr);
}
if(fullLayout.barmode === 'group') {
for(var i = 0; i < fullData.length; i++) {
traceOut = fullData[i];
if(traceOut.type === 'bar') {
traceIn = traceOut._input;
handleGroupingDefaults(traceIn, traceOut, fullLayout, coerce);
}
}
}
}
function handleText(traceIn, traceOut, layout, coerce, textposition, opts) {
opts = opts || {};
var moduleHasSelected = !(opts.moduleHasSelected === false);
var moduleHasUnselected = !(opts.moduleHasUnselected === false);
var moduleHasConstrain = !(opts.moduleHasConstrain === false);
var moduleHasCliponaxis = !(opts.moduleHasCliponaxis === false);
var moduleHasTextangle = !(opts.moduleHasTextangle === false);
var moduleHasInsideanchor = !(opts.moduleHasInsideanchor === false);
var hasPathbar = !!opts.hasPathbar;
var hasBoth = Array.isArray(textposition) || textposition === 'auto';
var hasInside = hasBoth || textposition === 'inside';
var hasOutside = hasBoth || textposition === 'outside';
if(hasInside || hasOutside) {
var dfltFont = coerceFont(coerce, 'textfont', layout.font);
// Note that coercing `insidetextfont` is always needed –
// even if `textposition` is `outside` for each trace – since
// an outside label can become an inside one, for example because
// of a bar being stacked on top of it.
var insideTextFontDefault = Lib.extendFlat({}, dfltFont);
var isTraceTextfontColorSet = traceIn.textfont && traceIn.textfont.color;
var isColorInheritedFromLayoutFont = !isTraceTextfontColorSet;
if(isColorInheritedFromLayoutFont) {
delete insideTextFontDefault.color;
}
coerceFont(coerce, 'insidetextfont', insideTextFontDefault);
if(hasPathbar) {
var pathbarTextFontDefault = Lib.extendFlat({}, dfltFont);
if(isColorInheritedFromLayoutFont) {
delete pathbarTextFontDefault.color;
}
coerceFont(coerce, 'pathbar.textfont', pathbarTextFontDefault);
}
if(hasOutside) coerceFont(coerce, 'outsidetextfont', dfltFont);
if(moduleHasSelected) coerce('selected.textfont.color');
if(moduleHasUnselected) coerce('unselected.textfont.color');
if(moduleHasConstrain) coerce('constraintext');
if(moduleHasCliponaxis) coerce('cliponaxis');
if(moduleHasTextangle) coerce('textangle');
coerce('texttemplate');
}
if(hasInside) {
if(moduleHasInsideanchor) coerce('insidetextanchor');
}
}
module.exports = {
supplyDefaults: supplyDefaults,
crossTraceDefaults: crossTraceDefaults,
handleGroupingDefaults: handleGroupingDefaults,
handleText: handleText
};
},{"../../components/color":366,"../../lib":503,"../../plots/cartesian/constraints":562,"../../registry":638,"../scatter/period_defaults":945,"../scatter/xy_defaults":952,"./attributes":648,"./style_defaults":663}],653:[function(_dereq_,module,exports){
'use strict';
module.exports = function eventData(out, pt, trace) {
// standard cartesian event data
out.x = 'xVal' in pt ? pt.xVal : pt.x;
out.y = 'yVal' in pt ? pt.yVal : pt.y;
if(pt.xa) out.xaxis = pt.xa;
if(pt.ya) out.yaxis = pt.ya;
if(trace.orientation === 'h') {
out.label = out.y;
out.value = out.x;
} else {
out.label = out.x;
out.value = out.y;
}
return out;
};
},{}],654:[function(_dereq_,module,exports){
'use strict';
var isNumeric = _dereq_('fast-isnumeric');
var tinycolor = _dereq_('tinycolor2');
var isArrayOrTypedArray = _dereq_('../../lib').isArrayOrTypedArray;
exports.coerceString = function(attributeDefinition, value, defaultValue) {
if(typeof value === 'string') {
if(value || !attributeDefinition.noBlank) return value;
} else if(typeof value === 'number' || value === true) {
if(!attributeDefinition.strict) return String(value);
}
return (defaultValue !== undefined) ?
defaultValue :
attributeDefinition.dflt;
};
exports.coerceNumber = function(attributeDefinition, value, defaultValue) {
if(isNumeric(value)) {
value = +value;
var min = attributeDefinition.min;
var max = attributeDefinition.max;
var isOutOfBounds = (min !== undefined && value < min) ||
(max !== undefined && value > max);
if(!isOutOfBounds) return value;
}
return (defaultValue !== undefined) ?
defaultValue :
attributeDefinition.dflt;
};
exports.coerceColor = function(attributeDefinition, value, defaultValue) {
if(tinycolor(value).isValid()) return value;
return (defaultValue !== undefined) ?
defaultValue :
attributeDefinition.dflt;
};
exports.coerceEnumerated = function(attributeDefinition, value, defaultValue) {
if(attributeDefinition.coerceNumber) value = +value;
if(attributeDefinition.values.indexOf(value) !== -1) return value;
return (defaultValue !== undefined) ?
defaultValue :
attributeDefinition.dflt;
};
exports.getValue = function(arrayOrScalar, index) {
var value;
if(!Array.isArray(arrayOrScalar)) value = arrayOrScalar;
else if(index < arrayOrScalar.length) value = arrayOrScalar[index];
return value;
};
exports.getLineWidth = function(trace, di) {
var w =
(0 < di.mlw) ? di.mlw :
!isArrayOrTypedArray(trace.marker.line.width) ? trace.marker.line.width :
0;
return w;
};
},{"../../lib":503,"fast-isnumeric":190,"tinycolor2":312}],655:[function(_dereq_,module,exports){
'use strict';
var Fx = _dereq_('../../components/fx');
var Registry = _dereq_('../../registry');
var Color = _dereq_('../../components/color');
var fillText = _dereq_('../../lib').fillText;
var getLineWidth = _dereq_('./helpers').getLineWidth;
var hoverLabelText = _dereq_('../../plots/cartesian/axes').hoverLabelText;
var BADNUM = _dereq_('../../constants/numerical').BADNUM;
function hoverPoints(pointData, xval, yval, hovermode, opts) {
var barPointData = hoverOnBars(pointData, xval, yval, hovermode, opts);
if(barPointData) {
var cd = barPointData.cd;
var trace = cd[0].trace;
var di = cd[barPointData.index];
barPointData.color = getTraceColor(trace, di);
Registry.getComponentMethod('errorbars', 'hoverInfo')(di, trace, barPointData);
return [barPointData];
}
}
function hoverOnBars(pointData, xval, yval, hovermode, opts) {
var cd = pointData.cd;
var trace = cd[0].trace;
var t = cd[0].t;
var isClosest = (hovermode === 'closest');
var isWaterfall = (trace.type === 'waterfall');
var maxHoverDistance = pointData.maxHoverDistance;
var maxSpikeDistance = pointData.maxSpikeDistance;
var posVal, sizeVal, posLetter, sizeLetter, dx, dy, pRangeCalc;
if(trace.orientation === 'h') {
posVal = yval;
sizeVal = xval;
posLetter = 'y';
sizeLetter = 'x';
dx = sizeFn;
dy = positionFn;
} else {
posVal = xval;
sizeVal = yval;
posLetter = 'x';
sizeLetter = 'y';
dy = sizeFn;
dx = positionFn;
}
var period = trace[posLetter + 'period'];
var isClosestOrPeriod = isClosest || period;
function thisBarMinPos(di) { return thisBarExtPos(di, -1); }
function thisBarMaxPos(di) { return thisBarExtPos(di, 1); }
function thisBarExtPos(di, sgn) {
var w = di.w;
return di[posLetter] + sgn * w / 2;
}
function periodLength(di) {
return di[posLetter + 'End'] - di[posLetter + 'Start'];
}
var minPos = isClosest ?
thisBarMinPos : period ?
function(di) {
return di.p - periodLength(di) / 2;
} :
function(di) {
/*
* In compare mode, accept a bar if you're on it *or* its group.
* Nearly always it's the group that matters, but in case the bar
* was explicitly set wider than its group we'd better accept the
* whole bar.
*
* use `bardelta` instead of `bargroupwidth` so we accept hover
* in the gap. That way hover doesn't flash on and off as you
* mouse over the plot in compare modes.
* In 'closest' mode though the flashing seems inevitable,
* without far more complex logic
*/
return Math.min(thisBarMinPos(di), di.p - t.bardelta / 2);
};
var maxPos = isClosest ?
thisBarMaxPos : period ?
function(di) {
return di.p + periodLength(di) / 2;
} :
function(di) {
return Math.max(thisBarMaxPos(di), di.p + t.bardelta / 2);
};
function inbox(_minPos, _maxPos, maxDistance) {
if(opts.finiteRange) maxDistance = 0;
// add a little to the pseudo-distance for wider bars, so that like scatter,
// if you are over two overlapping bars, the narrower one wins.
return Fx.inbox(_minPos - posVal, _maxPos - posVal,
maxDistance + Math.min(1, Math.abs(_maxPos - _minPos) / pRangeCalc) - 1);
}
function positionFn(di) {
return inbox(minPos(di), maxPos(di), maxHoverDistance);
}
function thisBarPositionFn(di) {
return inbox(thisBarMinPos(di), thisBarMaxPos(di), maxSpikeDistance);
}
function getSize(di) {
var s = di[sizeLetter];
if(isWaterfall) {
var rawS = Math.abs(di.rawS) || 0;
if(sizeVal > 0) {
s += rawS;
} else if(sizeVal < 0) {
s -= rawS;
}
}
return s;
}
function sizeFn(di) {
var v = sizeVal;
var b = di.b;
var s = getSize(di);
// add a gradient so hovering near the end of a
// bar makes it a little closer match
return Fx.inbox(b - v, s - v, maxHoverDistance + (s - v) / (s - b) - 1);
}
function thisBarSizeFn(di) {
var v = sizeVal;
var b = di.b;
var s = getSize(di);
// add a gradient so hovering near the end of a
// bar makes it a little closer match
return Fx.inbox(b - v, s - v, maxSpikeDistance + (s - v) / (s - b) - 1);
}
var pa = pointData[posLetter + 'a'];
var sa = pointData[sizeLetter + 'a'];
pRangeCalc = Math.abs(pa.r2c(pa.range[1]) - pa.r2c(pa.range[0]));
function dxy(di) { return (dx(di) + dy(di)) / 2; }
var distfn = Fx.getDistanceFunction(hovermode, dx, dy, dxy);
Fx.getClosest(cd, distfn, pointData);
// skip the rest (for this trace) if we didn't find a close point
if(pointData.index === false) return;
// skip points inside axis rangebreaks
if(cd[pointData.index].p === BADNUM) return;
// if we get here and we're not in 'closest' mode, push min/max pos back
// onto the group - even though that means occasionally the mouse will be
// over the hover label.
if(!isClosestOrPeriod) {
minPos = function(di) {
return Math.min(thisBarMinPos(di), di.p - t.bargroupwidth / 2);
};
maxPos = function(di) {
return Math.max(thisBarMaxPos(di), di.p + t.bargroupwidth / 2);
};
}
// the closest data point
var index = pointData.index;
var di = cd[index];
var size = (trace.base) ? di.b + di.s : di.s;
pointData[sizeLetter + '0'] = pointData[sizeLetter + '1'] = sa.c2p(di[sizeLetter], true);
pointData[sizeLetter + 'LabelVal'] = size;
var extent = t.extents[t.extents.round(di.p)];
pointData[posLetter + '0'] = pa.c2p(isClosest ? minPos(di) : extent[0], true);
pointData[posLetter + '1'] = pa.c2p(isClosest ? maxPos(di) : extent[1], true);
var hasPeriod = di.orig_p !== undefined;
pointData[posLetter + 'LabelVal'] = hasPeriod ? di.orig_p : di.p;
pointData.labelLabel = hoverLabelText(pa, pointData[posLetter + 'LabelVal'], trace[posLetter + 'hoverformat']);
pointData.valueLabel = hoverLabelText(sa, pointData[sizeLetter + 'LabelVal'], trace[sizeLetter + 'hoverformat']);
pointData.baseLabel = hoverLabelText(sa, di.b, trace[sizeLetter + 'hoverformat']);
// spikelines always want "closest" distance regardless of hovermode
pointData.spikeDistance = (thisBarSizeFn(di) + thisBarPositionFn(di)) / 2;
// they also want to point to the data value, regardless of where the label goes
// in case of bars shifted within groups
pointData[posLetter + 'Spike'] = pa.c2p(di.p, true);
fillText(di, trace, pointData);
pointData.hovertemplate = trace.hovertemplate;
return pointData;
}
function getTraceColor(trace, di) {
var mc = di.mcc || trace.marker.color;
var mlc = di.mlcc || trace.marker.line.color;
var mlw = getLineWidth(trace, di);
if(Color.opacity(mc)) return mc;
else if(Color.opacity(mlc) && mlw) return mlc;
}
module.exports = {
hoverPoints: hoverPoints,
hoverOnBars: hoverOnBars,
getTraceColor: getTraceColor
};
},{"../../components/color":366,"../../components/fx":406,"../../constants/numerical":479,"../../lib":503,"../../plots/cartesian/axes":554,"../../registry":638,"./helpers":654}],656:[function(_dereq_,module,exports){
'use strict';
module.exports = {
attributes: _dereq_('./attributes'),
layoutAttributes: _dereq_('./layout_attributes'),
supplyDefaults: _dereq_('./defaults').supplyDefaults,
crossTraceDefaults: _dereq_('./defaults').crossTraceDefaults,
supplyLayoutDefaults: _dereq_('./layout_defaults'),
calc: _dereq_('./calc'),
crossTraceCalc: _dereq_('./cross_trace_calc').crossTraceCalc,
colorbar: _dereq_('../scatter/marker_colorbar'),
arraysToCalcdata: _dereq_('./arrays_to_calcdata'),
plot: _dereq_('./plot').plot,
style: _dereq_('./style').style,
styleOnSelect: _dereq_('./style').styleOnSelect,
hoverPoints: _dereq_('./hover').hoverPoints,
eventData: _dereq_('./event_data'),
selectPoints: _dereq_('./select'),
moduleType: 'trace',
name: 'bar',
basePlotModule: _dereq_('../../plots/cartesian'),
categories: ['bar-like', 'cartesian', 'svg', 'bar', 'oriented', 'errorBarsOK', 'showLegend', 'zoomScale'],
animatable: true,
meta: {
}
};
},{"../../plots/cartesian":568,"../scatter/marker_colorbar":943,"./arrays_to_calcdata":647,"./attributes":648,"./calc":649,"./cross_trace_calc":651,"./defaults":652,"./event_data":653,"./hover":655,"./layout_attributes":657,"./layout_defaults":658,"./plot":659,"./select":660,"./style":662}],657:[function(_dereq_,module,exports){
'use strict';
module.exports = {
barmode: {
valType: 'enumerated',
values: ['stack', 'group', 'overlay', 'relative'],
dflt: 'group',
editType: 'calc',
},
barnorm: {
valType: 'enumerated',
values: ['', 'fraction', 'percent'],
dflt: '',
editType: 'calc',
},
bargap: {
valType: 'number',
min: 0,
max: 1,
editType: 'calc',
},
bargroupgap: {
valType: 'number',
min: 0,
max: 1,
dflt: 0,
editType: 'calc',
}
};
},{}],658:[function(_dereq_,module,exports){
'use strict';
var Registry = _dereq_('../../registry');
var Axes = _dereq_('../../plots/cartesian/axes');
var Lib = _dereq_('../../lib');
var layoutAttributes = _dereq_('./layout_attributes');
module.exports = function(layoutIn, layoutOut, fullData) {
function coerce(attr, dflt) {
return Lib.coerce(layoutIn, layoutOut, layoutAttributes, attr, dflt);
}
var hasBars = false;
var shouldBeGapless = false;
var gappedAnyway = false;
var usedSubplots = {};
var mode = coerce('barmode');
for(var i = 0; i < fullData.length; i++) {
var trace = fullData[i];
if(Registry.traceIs(trace, 'bar') && trace.visible) hasBars = true;
else continue;
// if we have at least 2 grouped bar traces on the same subplot,
// we should default to a gap anyway, even if the data is histograms
if(mode === 'group') {
var subploti = trace.xaxis + trace.yaxis;
if(usedSubplots[subploti]) gappedAnyway = true;
usedSubplots[subploti] = true;
}
if(trace.visible && trace.type === 'histogram') {
var pa = Axes.getFromId({_fullLayout: layoutOut},
trace[trace.orientation === 'v' ? 'xaxis' : 'yaxis']);
if(pa.type !== 'category') shouldBeGapless = true;
}
}
if(!hasBars) {
delete layoutOut.barmode;
return;
}
if(mode !== 'overlay') coerce('barnorm');
coerce('bargap', (shouldBeGapless && !gappedAnyway) ? 0 : 0.2);
coerce('bargroupgap');
};
},{"../../lib":503,"../../plots/cartesian/axes":554,"../../registry":638,"./layout_attributes":657}],659:[function(_dereq_,module,exports){
'use strict';
var d3 = _dereq_('@plotly/d3');
var isNumeric = _dereq_('fast-isnumeric');
var Lib = _dereq_('../../lib');
var svgTextUtils = _dereq_('../../lib/svg_text_utils');
var Color = _dereq_('../../components/color');
var Drawing = _dereq_('../../components/drawing');
var Registry = _dereq_('../../registry');
var tickText = _dereq_('../../plots/cartesian/axes').tickText;
var uniformText = _dereq_('./uniform_text');
var recordMinTextSize = uniformText.recordMinTextSize;
var clearMinTextSize = uniformText.clearMinTextSize;
var style = _dereq_('./style');
var helpers = _dereq_('./helpers');
var constants = _dereq_('./constants');
var attributes = _dereq_('./attributes');
var attributeText = attributes.text;
var attributeTextPosition = attributes.textposition;
var appendArrayPointValue = _dereq_('../../components/fx/helpers').appendArrayPointValue;
var TEXTPAD = constants.TEXTPAD;
function keyFunc(d) {return d.id;}
function getKeyFunc(trace) {
if(trace.ids) {
return keyFunc;
}
}
function dirSign(a, b) {
return (a < b) ? 1 : -1;
}
function getXY(di, xa, ya, isHorizontal) {
var s = [];
var p = [];
var sAxis = isHorizontal ? xa : ya;
var pAxis = isHorizontal ? ya : xa;
s[0] = sAxis.c2p(di.s0, true);
p[0] = pAxis.c2p(di.p0, true);
s[1] = sAxis.c2p(di.s1, true);
p[1] = pAxis.c2p(di.p1, true);
return isHorizontal ? [s, p] : [p, s];
}
function transition(selection, fullLayout, opts, makeOnCompleteCallback) {
if(!fullLayout.uniformtext.mode && hasTransition(opts)) {
var onComplete;
if(makeOnCompleteCallback) {
onComplete = makeOnCompleteCallback();
}
return selection
.transition()
.duration(opts.duration)
.ease(opts.easing)
.each('end', function() { onComplete && onComplete(); })
.each('interrupt', function() { onComplete && onComplete(); });
} else {
return selection;
}
}
function hasTransition(transitionOpts) {
return transitionOpts && transitionOpts.duration > 0;
}
function plot(gd, plotinfo, cdModule, traceLayer, opts, makeOnCompleteCallback) {
var xa = plotinfo.xaxis;
var ya = plotinfo.yaxis;
var fullLayout = gd._fullLayout;
if(!opts) {
opts = {
mode: fullLayout.barmode,
norm: fullLayout.barmode,
gap: fullLayout.bargap,
groupgap: fullLayout.bargroupgap
};
// don't clear bar when this is called from waterfall or funnel
clearMinTextSize('bar', fullLayout);
}
var bartraces = Lib.makeTraceGroups(traceLayer, cdModule, 'trace bars').each(function(cd) {
var plotGroup = d3.select(this);
var trace = cd[0].trace;
var isWaterfall = (trace.type === 'waterfall');
var isFunnel = (trace.type === 'funnel');
var isBar = (trace.type === 'bar');
var shouldDisplayZeros = (isBar || isFunnel);
var adjustPixel = 0;
if(isWaterfall && trace.connector.visible && trace.connector.mode === 'between') {
adjustPixel = trace.connector.line.width / 2;
}
var isHorizontal = (trace.orientation === 'h');
var withTransition = hasTransition(opts);
var pointGroup = Lib.ensureSingle(plotGroup, 'g', 'points');
var keyFunc = getKeyFunc(trace);
var bars = pointGroup.selectAll('g.point').data(Lib.identity, keyFunc);
bars.enter().append('g')
.classed('point', true);
bars.exit().remove();
bars.each(function(di, i) {
var bar = d3.select(this);
// now display the bar
// clipped xf/yf (2nd arg true): non-positive
// log values go off-screen by plotwidth
// so you see them continue if you drag the plot
var xy = getXY(di, xa, ya, isHorizontal);
var x0 = xy[0][0];
var x1 = xy[0][1];
var y0 = xy[1][0];
var y1 = xy[1][1];
// empty bars
var isBlank = (isHorizontal ? x1 - x0 : y1 - y0) === 0;
// display zeros if line.width > 0
if(isBlank && shouldDisplayZeros && helpers.getLineWidth(trace, di)) {
isBlank = false;
}
// skip nulls
if(!isBlank) {
isBlank = (
!isNumeric(x0) ||
!isNumeric(x1) ||
!isNumeric(y0) ||
!isNumeric(y1)
);
}
// record isBlank
di.isBlank = isBlank;
// for blank bars, ensure start and end positions are equal - important for smooth transitions
if(isBlank) {
if(isHorizontal) {
x1 = x0;
} else {
y1 = y0;
}
}
// in waterfall mode `between` we need to adjust bar end points to match the connector width
if(adjustPixel && !isBlank) {
if(isHorizontal) {
x0 -= dirSign(x0, x1) * adjustPixel;
x1 += dirSign(x0, x1) * adjustPixel;
} else {
y0 -= dirSign(y0, y1) * adjustPixel;
y1 += dirSign(y0, y1) * adjustPixel;
}
}
var lw;
var mc;
if(trace.type === 'waterfall') {
if(!isBlank) {
var cont = trace[di.dir].marker;
lw = cont.line.width;
mc = cont.color;
}
} else {
lw = helpers.getLineWidth(trace, di);
mc = di.mc || trace.marker.color;
}
function roundWithLine(v) {
var offset = d3.round((lw / 2) % 1, 2);
// if there are explicit gaps, don't round,
// it can make the gaps look crappy
return (opts.gap === 0 && opts.groupgap === 0) ?
d3.round(Math.round(v) - offset, 2) : v;
}
function expandToVisible(v, vc, hideZeroSpan) {
if(hideZeroSpan && v === vc) {
// should not expand zero span bars
// when start and end positions are identical
// i.e. for vertical when y0 === y1
// and for horizontal when x0 === x1
return v;
}
// if it's not in danger of disappearing entirely,
// round more precisely
return Math.abs(v - vc) >= 2 ? roundWithLine(v) :
// but if it's very thin, expand it so it's
// necessarily visible, even if it might overlap
// its neighbor
(v > vc ? Math.ceil(v) : Math.floor(v));
}
if(!gd._context.staticPlot) {
// if bars are not fully opaque or they have a line
// around them, round to integer pixels, mainly for
// safari so we prevent overlaps from its expansive
// pixelation. if the bars ARE fully opaque and have
// no line, expand to a full pixel to make sure we
// can see them
var op = Color.opacity(mc);
var fixpx = (op < 1 || lw > 0.01) ? roundWithLine : expandToVisible;
x0 = fixpx(x0, x1, isHorizontal);
x1 = fixpx(x1, x0, isHorizontal);
y0 = fixpx(y0, y1, !isHorizontal);
y1 = fixpx(y1, y0, !isHorizontal);
}
var sel = transition(Lib.ensureSingle(bar, 'path'), fullLayout, opts, makeOnCompleteCallback);
sel
.style('vector-effect', 'non-scaling-stroke')
.attr('d', (isNaN((x1 - x0) * (y1 - y0)) || (isBlank && gd._context.staticPlot)) ? 'M0,0Z' : 'M' + x0 + ',' + y0 + 'V' + y1 + 'H' + x1 + 'V' + y0 + 'Z')
.call(Drawing.setClipUrl, plotinfo.layerClipId, gd);
if(!fullLayout.uniformtext.mode && withTransition) {
var styleFns = Drawing.makePointStyleFns(trace);
Drawing.singlePointStyle(di, sel, trace, styleFns, gd);
}
appendBarText(gd, plotinfo, bar, cd, i, x0, x1, y0, y1, opts, makeOnCompleteCallback);
if(plotinfo.layerClipId) {
Drawing.hideOutsideRangePoint(di, bar.select('text'), xa, ya, trace.xcalendar, trace.ycalendar);
}
});
// lastly, clip points groups of `cliponaxis !== false` traces
// on `plotinfo._hasClipOnAxisFalse === true` subplots
var hasClipOnAxisFalse = trace.cliponaxis === false;
Drawing.setClipUrl(plotGroup, hasClipOnAxisFalse ? null : plotinfo.layerClipId, gd);
});
// error bars are on the top
Registry.getComponentMethod('errorbars', 'plot')(gd, bartraces, plotinfo, opts);
}
function appendBarText(gd, plotinfo, bar, cd, i, x0, x1, y0, y1, opts, makeOnCompleteCallback) {
var xa = plotinfo.xaxis;
var ya = plotinfo.yaxis;
var fullLayout = gd._fullLayout;
var textPosition;
function appendTextNode(bar, text, font) {
var textSelection = Lib.ensureSingle(bar, 'text')
.text(text)
.attr({
'class': 'bartext bartext-' + textPosition,
'text-anchor': 'middle',
// prohibit tex interpretation until we can handle
// tex and regular text together
'data-notex': 1
})
.call(Drawing.font, font)
.call(svgTextUtils.convertToTspans, gd);
return textSelection;
}
// get trace attributes
var trace = cd[0].trace;
var isHorizontal = (trace.orientation === 'h');
var text = getText(fullLayout, cd, i, xa, ya);
textPosition = getTextPosition(trace, i);
// compute text position
var inStackOrRelativeMode =
opts.mode === 'stack' ||
opts.mode === 'relative';
var calcBar = cd[i];
var isOutmostBar = !inStackOrRelativeMode || calcBar._outmost;
if(!text ||
textPosition === 'none' ||
((calcBar.isBlank || x0 === x1 || y0 === y1) && (
textPosition === 'auto' ||
textPosition === 'inside'))) {
bar.select('text').remove();
return;
}
var layoutFont = fullLayout.font;
var barColor = style.getBarColor(cd[i], trace);
var insideTextFont = style.getInsideTextFont(trace, i, layoutFont, barColor);
var outsideTextFont = style.getOutsideTextFont(trace, i, layoutFont);
// Special case: don't use the c2p(v, true) value on log size axes,
// so that we can get correctly inside text scaling
var di = bar.datum();
if(isHorizontal) {
if(xa.type === 'log' && di.s0 <= 0) {
if(xa.range[0] < xa.range[1]) {
x0 = 0;
} else {
x0 = xa._length;
}
}
} else {
if(ya.type === 'log' && di.s0 <= 0) {
if(ya.range[0] < ya.range[1]) {
y0 = ya._length;
} else {
y0 = 0;
}
}
}
// padding excluded
var barWidth = Math.abs(x1 - x0) - 2 * TEXTPAD;
var barHeight = Math.abs(y1 - y0) - 2 * TEXTPAD;
var textSelection;
var textBB;
var textWidth;
var textHeight;
var font;
if(textPosition === 'outside') {
if(!isOutmostBar && !calcBar.hasB) textPosition = 'inside';
}
if(textPosition === 'auto') {
if(isOutmostBar) {
// draw text using insideTextFont and check if it fits inside bar
textPosition = 'inside';
font = Lib.ensureUniformFontSize(gd, insideTextFont);
textSelection = appendTextNode(bar, text, font);
textBB = Drawing.bBox(textSelection.node()),
textWidth = textBB.width,
textHeight = textBB.height;
var textHasSize = (textWidth > 0 && textHeight > 0);
var fitsInside = (textWidth <= barWidth && textHeight <= barHeight);
var fitsInsideIfRotated = (textWidth <= barHeight && textHeight <= barWidth);
var fitsInsideIfShrunk = (isHorizontal) ?
(barWidth >= textWidth * (barHeight / textHeight)) :
(barHeight >= textHeight * (barWidth / textWidth));
if(textHasSize && (
fitsInside ||
fitsInsideIfRotated ||
fitsInsideIfShrunk)
) {
textPosition = 'inside';
} else {
textPosition = 'outside';
textSelection.remove();
textSelection = null;
}
} else {
textPosition = 'inside';
}
}
if(!textSelection) {
font = Lib.ensureUniformFontSize(gd, (textPosition === 'outside') ? outsideTextFont : insideTextFont);
textSelection = appendTextNode(bar, text, font);
var currentTransform = textSelection.attr('transform');
textSelection.attr('transform', '');
textBB = Drawing.bBox(textSelection.node()),
textWidth = textBB.width,
textHeight = textBB.height;
textSelection.attr('transform', currentTransform);
if(textWidth <= 0 || textHeight <= 0) {
textSelection.remove();
return;
}
}
var angle = trace.textangle;
// compute text transform
var transform, constrained;
if(textPosition === 'outside') {
constrained =
trace.constraintext === 'both' ||
trace.constraintext === 'outside';
transform = toMoveOutsideBar(x0, x1, y0, y1, textBB, {
isHorizontal: isHorizontal,
constrained: constrained,
angle: angle
});
} else {
constrained =
trace.constraintext === 'both' ||
trace.constraintext === 'inside';
transform = toMoveInsideBar(x0, x1, y0, y1, textBB, {
isHorizontal: isHorizontal,
constrained: constrained,
angle: angle,
anchor: trace.insidetextanchor
});
}
transform.fontSize = font.size;
recordMinTextSize(trace.type, transform, fullLayout);
calcBar.transform = transform;
transition(textSelection, fullLayout, opts, makeOnCompleteCallback)
.attr('transform', Lib.getTextTransform(transform));
}
function getRotateFromAngle(angle) {
return (angle === 'auto') ? 0 : angle;
}
function getRotatedTextSize(textBB, rotate) {
var a = Math.PI / 180 * rotate;
var absSin = Math.abs(Math.sin(a));
var absCos = Math.abs(Math.cos(a));
return {
x: textBB.width * absCos + textBB.height * absSin,
y: textBB.width * absSin + textBB.height * absCos
};
}
function toMoveInsideBar(x0, x1, y0, y1, textBB, opts) {
var isHorizontal = !!opts.isHorizontal;
var constrained = !!opts.constrained;
var angle = opts.angle || 0;
var anchor = opts.anchor || 'end';
var isEnd = anchor === 'end';
var isStart = anchor === 'start';
var leftToRight = opts.leftToRight || 0; // left: -1, center: 0, right: 1
var toRight = (leftToRight + 1) / 2;
var toLeft = 1 - toRight;
var textWidth = textBB.width;
var textHeight = textBB.height;
var lx = Math.abs(x1 - x0);
var ly = Math.abs(y1 - y0);
// compute remaining space
var textpad = (
lx > (2 * TEXTPAD) &&
ly > (2 * TEXTPAD)
) ? TEXTPAD : 0;
lx -= 2 * textpad;
ly -= 2 * textpad;
var rotate = getRotateFromAngle(angle);
if((angle === 'auto') &&
!(textWidth <= lx && textHeight <= ly) &&
(textWidth > lx || textHeight > ly) && (
!(textWidth > ly || textHeight > lx) ||
((textWidth < textHeight) !== (lx < ly))
)) {
rotate += 90;
}
var t = getRotatedTextSize(textBB, rotate);
var scale = 1;
if(constrained) {
scale = Math.min(
1,
lx / t.x,
ly / t.y
);
}
// compute text and target positions
var textX = (
textBB.left * toLeft +
textBB.right * toRight
);
var textY = (textBB.top + textBB.bottom) / 2;
var targetX = (
(x0 + TEXTPAD) * toLeft +
(x1 - TEXTPAD) * toRight
);
var targetY = (y0 + y1) / 2;
var anchorX = 0;
var anchorY = 0;
if(isStart || isEnd) {
var extrapad = (isHorizontal ? t.x : t.y) / 2;
var dir = isHorizontal ? dirSign(x0, x1) : dirSign(y0, y1);
if(isHorizontal) {
if(isStart) {
targetX = x0 + dir * textpad;
anchorX = -dir * extrapad;
} else {
targetX = x1 - dir * textpad;
anchorX = dir * extrapad;
}
} else {
if(isStart) {
targetY = y0 + dir * textpad;
anchorY = -dir * extrapad;
} else {
targetY = y1 - dir * textpad;
anchorY = dir * extrapad;
}
}
}
return {
textX: textX,
textY: textY,
targetX: targetX,
targetY: targetY,
anchorX: anchorX,
anchorY: anchorY,
scale: scale,
rotate: rotate
};
}
function toMoveOutsideBar(x0, x1, y0, y1, textBB, opts) {
var isHorizontal = !!opts.isHorizontal;
var constrained = !!opts.constrained;
var angle = opts.angle || 0;
var textWidth = textBB.width;
var textHeight = textBB.height;
var lx = Math.abs(x1 - x0);
var ly = Math.abs(y1 - y0);
var textpad;
// Keep the padding so the text doesn't sit right against
// the bars, but don't factor it into barWidth
if(isHorizontal) {
textpad = (ly > 2 * TEXTPAD) ? TEXTPAD : 0;
} else {
textpad = (lx > 2 * TEXTPAD) ? TEXTPAD : 0;
}
// compute rotate and scale
var scale = 1;
if(constrained) {
scale = (isHorizontal) ?
Math.min(1, ly / textHeight) :
Math.min(1, lx / textWidth);
}
var rotate = getRotateFromAngle(angle);
var t = getRotatedTextSize(textBB, rotate);
// compute text and target positions
var extrapad = (isHorizontal ? t.x : t.y) / 2;
var textX = (textBB.left + textBB.right) / 2;
var textY = (textBB.top + textBB.bottom) / 2;
var targetX = (x0 + x1) / 2;
var targetY = (y0 + y1) / 2;
var anchorX = 0;
var anchorY = 0;
var dir = isHorizontal ? dirSign(x1, x0) : dirSign(y0, y1);
if(isHorizontal) {
targetX = x1 - dir * textpad;
anchorX = dir * extrapad;
} else {
targetY = y1 + dir * textpad;
anchorY = -dir * extrapad;
}
return {
textX: textX,
textY: textY,
targetX: targetX,
targetY: targetY,
anchorX: anchorX,
anchorY: anchorY,
scale: scale,
rotate: rotate
};
}
function getText(fullLayout, cd, index, xa, ya) {
var trace = cd[0].trace;
var texttemplate = trace.texttemplate;
var value;
if(texttemplate) {
value = calcTexttemplate(fullLayout, cd, index, xa, ya);
} else if(trace.textinfo) {
value = calcTextinfo(cd, index, xa, ya);
} else {
value = helpers.getValue(trace.text, index);
}
return helpers.coerceString(attributeText, value);
}
function getTextPosition(trace, index) {
var value = helpers.getValue(trace.textposition, index);
return helpers.coerceEnumerated(attributeTextPosition, value);
}
function calcTexttemplate(fullLayout, cd, index, xa, ya) {
var trace = cd[0].trace;
var texttemplate = Lib.castOption(trace, index, 'texttemplate');
if(!texttemplate) return '';
var isWaterfall = (trace.type === 'waterfall');
var isFunnel = (trace.type === 'funnel');
var pLetter, pAxis;
var vLetter, vAxis;
if(trace.orientation === 'h') {
pLetter = 'y';
pAxis = ya;
vLetter = 'x';
vAxis = xa;
} else {
pLetter = 'x';
pAxis = xa;
vLetter = 'y';
vAxis = ya;
}
function formatLabel(u) {
return tickText(pAxis, pAxis.c2l(u), true).text;
}
function formatNumber(v) {
return tickText(vAxis, vAxis.c2l(v), true).text;
}
var cdi = cd[index];
var obj = {};
obj.label = cdi.p;
obj.labelLabel = obj[pLetter + 'Label'] = formatLabel(cdi.p);
var tx = Lib.castOption(trace, cdi.i, 'text');
if(tx === 0 || tx) obj.text = tx;
obj.value = cdi.s;
obj.valueLabel = obj[vLetter + 'Label'] = formatNumber(cdi.s);
var pt = {};
appendArrayPointValue(pt, trace, cdi.i);
if(isWaterfall) {
obj.delta = +cdi.rawS || cdi.s;
obj.deltaLabel = formatNumber(obj.delta);
obj.final = cdi.v;
obj.finalLabel = formatNumber(obj.final);
obj.initial = obj.final - obj.delta;
obj.initialLabel = formatNumber(obj.initial);
}
if(isFunnel) {
obj.value = cdi.s;
obj.valueLabel = formatNumber(obj.value);
obj.percentInitial = cdi.begR;
obj.percentInitialLabel = Lib.formatPercent(cdi.begR);
obj.percentPrevious = cdi.difR;
obj.percentPreviousLabel = Lib.formatPercent(cdi.difR);
obj.percentTotal = cdi.sumR;
obj.percenTotalLabel = Lib.formatPercent(cdi.sumR);
}
var customdata = Lib.castOption(trace, cdi.i, 'customdata');
if(customdata) obj.customdata = customdata;
return Lib.texttemplateString(texttemplate, obj, fullLayout._d3locale, pt, obj, trace._meta || {});
}
function calcTextinfo(cd, index, xa, ya) {
var trace = cd[0].trace;
var isHorizontal = (trace.orientation === 'h');
var isWaterfall = (trace.type === 'waterfall');
var isFunnel = (trace.type === 'funnel');
function formatLabel(u) {
var pAxis = isHorizontal ? ya : xa;
return tickText(pAxis, u, true).text;
}
function formatNumber(v) {
var sAxis = isHorizontal ? xa : ya;
return tickText(sAxis, +v, true).text;
}
var textinfo = trace.textinfo;
var cdi = cd[index];
var parts = textinfo.split('+');
var text = [];
var tx;
var hasFlag = function(flag) { return parts.indexOf(flag) !== -1; };
if(hasFlag('label')) {
text.push(formatLabel(cd[index].p));
}
if(hasFlag('text')) {
tx = Lib.castOption(trace, cdi.i, 'text');
if(tx === 0 || tx) text.push(tx);
}
if(isWaterfall) {
var delta = +cdi.rawS || cdi.s;
var final = cdi.v;
var initial = final - delta;
if(hasFlag('initial')) text.push(formatNumber(initial));
if(hasFlag('delta')) text.push(formatNumber(delta));
if(hasFlag('final')) text.push(formatNumber(final));
}
if(isFunnel) {
if(hasFlag('value')) text.push(formatNumber(cdi.s));
var nPercent = 0;
if(hasFlag('percent initial')) nPercent++;
if(hasFlag('percent previous')) nPercent++;
if(hasFlag('percent total')) nPercent++;
var hasMultiplePercents = nPercent > 1;
if(hasFlag('percent initial')) {
tx = Lib.formatPercent(cdi.begR);
if(hasMultiplePercents) tx += ' of initial';
text.push(tx);
}
if(hasFlag('percent previous')) {
tx = Lib.formatPercent(cdi.difR);
if(hasMultiplePercents) tx += ' of previous';
text.push(tx);
}
if(hasFlag('percent total')) {
tx = Lib.formatPercent(cdi.sumR);
if(hasMultiplePercents) tx += ' of total';
text.push(tx);
}
}
return text.join('
');
}
module.exports = {
plot: plot,
toMoveInsideBar: toMoveInsideBar
};
},{"../../components/color":366,"../../components/drawing":388,"../../components/fx/helpers":402,"../../lib":503,"../../lib/svg_text_utils":529,"../../plots/cartesian/axes":554,"../../registry":638,"./attributes":648,"./constants":650,"./helpers":654,"./style":662,"./uniform_text":664,"@plotly/d3":58,"fast-isnumeric":190}],660:[function(_dereq_,module,exports){
'use strict';
module.exports = function selectPoints(searchInfo, selectionTester) {
var cd = searchInfo.cd;
var xa = searchInfo.xaxis;
var ya = searchInfo.yaxis;
var trace = cd[0].trace;
var isFunnel = (trace.type === 'funnel');
var isHorizontal = (trace.orientation === 'h');
var selection = [];
var i;
if(selectionTester === false) {
// clear selection
for(i = 0; i < cd.length; i++) {
cd[i].selected = 0;
}
} else {
for(i = 0; i < cd.length; i++) {
var di = cd[i];
var ct = 'ct' in di ? di.ct : getCentroid(di, xa, ya, isHorizontal, isFunnel);
if(selectionTester.contains(ct, false, i, searchInfo)) {
selection.push({
pointNumber: i,
x: xa.c2d(di.x),
y: ya.c2d(di.y)
});
di.selected = 1;
} else {
di.selected = 0;
}
}
}
return selection;
};
function getCentroid(d, xa, ya, isHorizontal, isFunnel) {
var x0 = xa.c2p(isHorizontal ? d.s0 : d.p0, true);
var x1 = xa.c2p(isHorizontal ? d.s1 : d.p1, true);
var y0 = ya.c2p(isHorizontal ? d.p0 : d.s0, true);
var y1 = ya.c2p(isHorizontal ? d.p1 : d.s1, true);
if(isFunnel) {
return [(x0 + x1) / 2, (y0 + y1) / 2];
} else {
if(isHorizontal) {
return [x1, (y0 + y1) / 2];
} else {
return [(x0 + x1) / 2, y1];
}
}
}
},{}],661:[function(_dereq_,module,exports){
'use strict';
module.exports = Sieve;
var distinctVals = _dereq_('../../lib').distinctVals;
var BADNUM = _dereq_('../../constants/numerical').BADNUM;
/**
* Helper class to sieve data from traces into bins
*
* @class
*
* @param {Array} traces
* Array of calculated traces
* @param {object} opts
* - @param {boolean} [sepNegVal]
* If true, then split data at the same position into a bar
* for positive values and another for negative values
* - @param {boolean} [overlapNoMerge]
* If true, then don't merge overlapping bars into a single bar
*/
function Sieve(traces, opts) {
this.traces = traces;
this.sepNegVal = opts.sepNegVal;
this.overlapNoMerge = opts.overlapNoMerge;
// for single-bin histograms - see histogram/calc
var width1 = Infinity;
var positions = [];
for(var i = 0; i < traces.length; i++) {
var trace = traces[i];
for(var j = 0; j < trace.length; j++) {
var bar = trace[j];
if(bar.p !== BADNUM) positions.push(bar.p);
}
if(trace[0] && trace[0].width1) {
width1 = Math.min(trace[0].width1, width1);
}
}
this.positions = positions;
var dv = distinctVals(positions);
this.distinctPositions = dv.vals;
if(dv.vals.length === 1 && width1 !== Infinity) this.minDiff = width1;
else this.minDiff = Math.min(dv.minDiff, width1);
var type = (opts.posAxis || {}).type;
if(type === 'category' || type === 'multicategory') {
this.minDiff = 1;
}
this.binWidth = this.minDiff;
this.bins = {};
}
/**
* Sieve datum
*
* @method
* @param {number} position
* @param {number} value
* @returns {number} Previous bin value
*/
Sieve.prototype.put = function put(position, value) {
var label = this.getLabel(position, value);
var oldValue = this.bins[label] || 0;
this.bins[label] = oldValue + value;
return oldValue;
};
/**
* Get current bin value for a given datum
*
* @method
* @param {number} position Position of datum
* @param {number} [value] Value of datum
* (required if this.sepNegVal is true)
* @returns {number} Current bin value
*/
Sieve.prototype.get = function get(position, value) {
var label = this.getLabel(position, value);
return this.bins[label] || 0;
};
/**
* Get bin label for a given datum
*
* @method
* @param {number} position Position of datum
* @param {number} [value] Value of datum
* (required if this.sepNegVal is true)
* @returns {string} Bin label
* (prefixed with a 'v' if value is negative and this.sepNegVal is
* true; otherwise prefixed with '^')
*/
Sieve.prototype.getLabel = function getLabel(position, value) {
var prefix = (value < 0 && this.sepNegVal) ? 'v' : '^';
var label = (this.overlapNoMerge) ?
position :
Math.round(position / this.binWidth);
return prefix + label;
};
},{"../../constants/numerical":479,"../../lib":503}],662:[function(_dereq_,module,exports){
'use strict';
var d3 = _dereq_('@plotly/d3');
var Color = _dereq_('../../components/color');
var Drawing = _dereq_('../../components/drawing');
var Lib = _dereq_('../../lib');
var Registry = _dereq_('../../registry');
var resizeText = _dereq_('./uniform_text').resizeText;
var attributes = _dereq_('./attributes');
var attributeTextFont = attributes.textfont;
var attributeInsideTextFont = attributes.insidetextfont;
var attributeOutsideTextFont = attributes.outsidetextfont;
var helpers = _dereq_('./helpers');
function style(gd) {
var s = d3.select(gd).selectAll('g.barlayer').selectAll('g.trace');
resizeText(gd, s, 'bar');
var barcount = s.size();
var fullLayout = gd._fullLayout;
// trace styling
s.style('opacity', function(d) { return d[0].trace.opacity; })
// for gapless (either stacked or neighboring grouped) bars use
// crispEdges to turn off antialiasing so an artificial gap
// isn't introduced.
.each(function(d) {
if((fullLayout.barmode === 'stack' && barcount > 1) ||
(fullLayout.bargap === 0 &&
fullLayout.bargroupgap === 0 &&
!d[0].trace.marker.line.width)) {
d3.select(this).attr('shape-rendering', 'crispEdges');
}
});
s.selectAll('g.points').each(function(d) {
var sel = d3.select(this);
var trace = d[0].trace;
stylePoints(sel, trace, gd);
});
Registry.getComponentMethod('errorbars', 'style')(s);
}
function stylePoints(sel, trace, gd) {
Drawing.pointStyle(sel.selectAll('path'), trace, gd);
styleTextPoints(sel, trace, gd);
}
function styleTextPoints(sel, trace, gd) {
sel.selectAll('text').each(function(d) {
var tx = d3.select(this);
var font = Lib.ensureUniformFontSize(gd, determineFont(tx, d, trace, gd));
Drawing.font(tx, font);
});
}
function styleOnSelect(gd, cd, sel) {
var trace = cd[0].trace;
if(trace.selectedpoints) {
stylePointsInSelectionMode(sel, trace, gd);
} else {
stylePoints(sel, trace, gd);
Registry.getComponentMethod('errorbars', 'style')(sel);
}
}
function stylePointsInSelectionMode(s, trace, gd) {
Drawing.selectedPointStyle(s.selectAll('path'), trace);
styleTextInSelectionMode(s.selectAll('text'), trace, gd);
}
function styleTextInSelectionMode(txs, trace, gd) {
txs.each(function(d) {
var tx = d3.select(this);
var font;
if(d.selected) {
font = Lib.ensureUniformFontSize(gd, determineFont(tx, d, trace, gd));
var selectedFontColor = trace.selected.textfont && trace.selected.textfont.color;
if(selectedFontColor) {
font.color = selectedFontColor;
}
Drawing.font(tx, font);
} else {
Drawing.selectedTextStyle(tx, trace);
}
});
}
function determineFont(tx, d, trace, gd) {
var layoutFont = gd._fullLayout.font;
var textFont = trace.textfont;
if(tx.classed('bartext-inside')) {
var barColor = getBarColor(d, trace);
textFont = getInsideTextFont(trace, d.i, layoutFont, barColor);
} else if(tx.classed('bartext-outside')) {
textFont = getOutsideTextFont(trace, d.i, layoutFont);
}
return textFont;
}
function getTextFont(trace, index, defaultValue) {
return getFontValue(
attributeTextFont, trace.textfont, index, defaultValue);
}
function getInsideTextFont(trace, index, layoutFont, barColor) {
var defaultFont = getTextFont(trace, index, layoutFont);
var wouldFallBackToLayoutFont =
(trace._input.textfont === undefined || trace._input.textfont.color === undefined) ||
(Array.isArray(trace.textfont.color) && trace.textfont.color[index] === undefined);
if(wouldFallBackToLayoutFont) {
defaultFont = {
color: Color.contrast(barColor),
family: defaultFont.family,
size: defaultFont.size
};
}
return getFontValue(
attributeInsideTextFont, trace.insidetextfont, index, defaultFont);
}
function getOutsideTextFont(trace, index, layoutFont) {
var defaultFont = getTextFont(trace, index, layoutFont);
return getFontValue(
attributeOutsideTextFont, trace.outsidetextfont, index, defaultFont);
}
function getFontValue(attributeDefinition, attributeValue, index, defaultValue) {
attributeValue = attributeValue || {};
var familyValue = helpers.getValue(attributeValue.family, index);
var sizeValue = helpers.getValue(attributeValue.size, index);
var colorValue = helpers.getValue(attributeValue.color, index);
return {
family: helpers.coerceString(
attributeDefinition.family, familyValue, defaultValue.family),
size: helpers.coerceNumber(
attributeDefinition.size, sizeValue, defaultValue.size),
color: helpers.coerceColor(
attributeDefinition.color, colorValue, defaultValue.color)
};
}
function getBarColor(cd, trace) {
if(trace.type === 'waterfall') {
return trace[cd.dir].marker.color;
}
return cd.mcc || cd.mc || trace.marker.color;
}
module.exports = {
style: style,
styleTextPoints: styleTextPoints,
styleOnSelect: styleOnSelect,
getInsideTextFont: getInsideTextFont,
getOutsideTextFont: getOutsideTextFont,
getBarColor: getBarColor,
resizeText: resizeText
};
},{"../../components/color":366,"../../components/drawing":388,"../../lib":503,"../../registry":638,"./attributes":648,"./helpers":654,"./uniform_text":664,"@plotly/d3":58}],663:[function(_dereq_,module,exports){
'use strict';
var Color = _dereq_('../../components/color');
var hasColorscale = _dereq_('../../components/colorscale/helpers').hasColorscale;
var colorscaleDefaults = _dereq_('../../components/colorscale/defaults');
var coercePattern = _dereq_('../../lib').coercePattern;
module.exports = function handleStyleDefaults(traceIn, traceOut, coerce, defaultColor, layout) {
var markerColor = coerce('marker.color', defaultColor);
var hasMarkerColorscale = hasColorscale(traceIn, 'marker');
if(hasMarkerColorscale) {
colorscaleDefaults(
traceIn, traceOut, layout, coerce, {prefix: 'marker.', cLetter: 'c'}
);
}
coerce('marker.line.color', Color.defaultLine);
if(hasColorscale(traceIn, 'marker.line')) {
colorscaleDefaults(
traceIn, traceOut, layout, coerce, {prefix: 'marker.line.', cLetter: 'c'}
);
}
coerce('marker.line.width');
coerce('marker.opacity');
coercePattern(coerce, 'marker.pattern', markerColor, hasMarkerColorscale);
coerce('selected.marker.color');
coerce('unselected.marker.color');
};
},{"../../components/color":366,"../../components/colorscale/defaults":376,"../../components/colorscale/helpers":377,"../../lib":503}],664:[function(_dereq_,module,exports){
'use strict';
var d3 = _dereq_('@plotly/d3');
var Lib = _dereq_('../../lib');
function resizeText(gd, gTrace, traceType) {
var fullLayout = gd._fullLayout;
var minSize = fullLayout['_' + traceType + 'Text_minsize'];
if(minSize) {
var shouldHide = fullLayout.uniformtext.mode === 'hide';
var selector;
switch(traceType) {
case 'funnelarea' :
case 'pie' :
case 'sunburst' :
selector = 'g.slice';
break;
case 'treemap' :
case 'icicle' :
selector = 'g.slice, g.pathbar';
break;
default :
selector = 'g.points > g.point';
}
gTrace.selectAll(selector).each(function(d) {
var transform = d.transform;
if(transform) {
transform.scale = (shouldHide && transform.hide) ? 0 : minSize / transform.fontSize;
var el = d3.select(this).select('text');
el.attr('transform', Lib.getTextTransform(transform));
}
});
}
}
function recordMinTextSize(
traceType, // in
transform, // inout
fullLayout // inout
) {
if(fullLayout.uniformtext.mode) {
var minKey = getMinKey(traceType);
var minSize = fullLayout.uniformtext.minsize;
var size = transform.scale * transform.fontSize;
transform.hide = size < minSize;
fullLayout[minKey] = fullLayout[minKey] || Infinity;
if(!transform.hide) {
fullLayout[minKey] = Math.min(
fullLayout[minKey],
Math.max(size, minSize)
);
}
}
}
function clearMinTextSize(
traceType, // in
fullLayout // inout
) {
var minKey = getMinKey(traceType);
fullLayout[minKey] = undefined;
}
function getMinKey(traceType) {
return '_' + traceType + 'Text_minsize';
}
module.exports = {
recordMinTextSize: recordMinTextSize,
clearMinTextSize: clearMinTextSize,
resizeText: resizeText
};
},{"../../lib":503,"@plotly/d3":58}],665:[function(_dereq_,module,exports){
'use strict';
var hovertemplateAttrs = _dereq_('../../plots/template_attributes').hovertemplateAttrs;
var extendFlat = _dereq_('../../lib/extend').extendFlat;
var scatterPolarAttrs = _dereq_('../scatterpolar/attributes');
var barAttrs = _dereq_('../bar/attributes');
module.exports = {
r: scatterPolarAttrs.r,
theta: scatterPolarAttrs.theta,
r0: scatterPolarAttrs.r0,
dr: scatterPolarAttrs.dr,
theta0: scatterPolarAttrs.theta0,
dtheta: scatterPolarAttrs.dtheta,
thetaunit: scatterPolarAttrs.thetaunit,
// orientation: {
// valType: 'enumerated',
// values: ['radial', 'angular'],
// editType: 'calc+clearAxisTypes',
//
// },
base: extendFlat({}, barAttrs.base, {
}),
offset: extendFlat({}, barAttrs.offset, {
}),
width: extendFlat({}, barAttrs.width, {
}),
text: extendFlat({}, barAttrs.text, {
}),
hovertext: extendFlat({}, barAttrs.hovertext, {
}),
// textposition: {},
// textfont: {},
// insidetextfont: {},
// outsidetextfont: {},
// constraintext: {},
// cliponaxis: extendFlat({}, barAttrs.cliponaxis, {dflt: false}),
marker: barAttrs.marker,
hoverinfo: scatterPolarAttrs.hoverinfo,
hovertemplate: hovertemplateAttrs(),
selected: barAttrs.selected,
unselected: barAttrs.unselected
// error_x (error_r, error_theta)
// error_y
};
},{"../../lib/extend":493,"../../plots/template_attributes":633,"../bar/attributes":648,"../scatterpolar/attributes":999}],666:[function(_dereq_,module,exports){
'use strict';
var hasColorscale = _dereq_('../../components/colorscale/helpers').hasColorscale;
var colorscaleCalc = _dereq_('../../components/colorscale/calc');
var arraysToCalcdata = _dereq_('../bar/arrays_to_calcdata');
var setGroupPositions = _dereq_('../bar/cross_trace_calc').setGroupPositions;
var calcSelection = _dereq_('../scatter/calc_selection');
var traceIs = _dereq_('../../registry').traceIs;
var extendFlat = _dereq_('../../lib').extendFlat;
function calc(gd, trace) {
var fullLayout = gd._fullLayout;
var subplotId = trace.subplot;
var radialAxis = fullLayout[subplotId].radialaxis;
var angularAxis = fullLayout[subplotId].angularaxis;
var rArray = radialAxis.makeCalcdata(trace, 'r');
var thetaArray = angularAxis.makeCalcdata(trace, 'theta');
var len = trace._length;
var cd = new Array(len);
// 'size' axis variables
var sArray = rArray;
// 'pos' axis variables
var pArray = thetaArray;
for(var i = 0; i < len; i++) {
cd[i] = {p: pArray[i], s: sArray[i]};
}
// convert width and offset in 'c' coordinate,
// set 'c' value(s) in trace._width and trace._offset,
// to make Bar.crossTraceCalc "just work"
function d2c(attr) {
var val = trace[attr];
if(val !== undefined) {
trace['_' + attr] = Array.isArray(val) ?
angularAxis.makeCalcdata(trace, attr) :
angularAxis.d2c(val, trace.thetaunit);
}
}
if(angularAxis.type === 'linear') {
d2c('width');
d2c('offset');
}
if(hasColorscale(trace, 'marker')) {
colorscaleCalc(gd, trace, {
vals: trace.marker.color,
containerStr: 'marker',
cLetter: 'c'
});
}
if(hasColorscale(trace, 'marker.line')) {
colorscaleCalc(gd, trace, {
vals: trace.marker.line.color,
containerStr: 'marker.line',
cLetter: 'c'
});
}
arraysToCalcdata(cd, trace);
calcSelection(cd, trace);
return cd;
}
function crossTraceCalc(gd, polarLayout, subplotId) {
var calcdata = gd.calcdata;
var barPolarCd = [];
for(var i = 0; i < calcdata.length; i++) {
var cdi = calcdata[i];
var trace = cdi[0].trace;
if(trace.visible === true && traceIs(trace, 'bar') &&
trace.subplot === subplotId
) {
barPolarCd.push(cdi);
}
}
// to make _extremes is filled in correctly so that
// polar._subplot.radialAxis can get auotrange'd
// TODO clean up!
// I think we want to call getAutorange on polar.radialaxis
// NOT on polar._subplot.radialAxis
var rAxis = extendFlat({}, polarLayout.radialaxis, {_id: 'x'});
var aAxis = polarLayout.angularaxis;
setGroupPositions(gd, aAxis, rAxis, barPolarCd, {
mode: polarLayout.barmode,
norm: polarLayout.barnorm,
gap: polarLayout.bargap,
groupgap: polarLayout.bargroupgap
});
}
module.exports = {
calc: calc,
crossTraceCalc: crossTraceCalc
};
},{"../../components/colorscale/calc":374,"../../components/colorscale/helpers":377,"../../lib":503,"../../registry":638,"../bar/arrays_to_calcdata":647,"../bar/cross_trace_calc":651,"../scatter/calc_selection":927}],667:[function(_dereq_,module,exports){
'use strict';
var Lib = _dereq_('../../lib');
var handleRThetaDefaults = _dereq_('../scatterpolar/defaults').handleRThetaDefaults;
var handleStyleDefaults = _dereq_('../bar/style_defaults');
var attributes = _dereq_('./attributes');
module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout) {
function coerce(attr, dflt) {
return Lib.coerce(traceIn, traceOut, attributes, attr, dflt);
}
var len = handleRThetaDefaults(traceIn, traceOut, layout, coerce);
if(!len) {
traceOut.visible = false;
return;
}
// coerce('orientation', (traceOut.theta && !traceOut.r) ? 'angular' : 'radial');
coerce('thetaunit');
coerce('base');
coerce('offset');
coerce('width');
coerce('text');
coerce('hovertext');
coerce('hovertemplate');
// var textPosition = coerce('textposition');
// var hasBoth = Array.isArray(textPosition) || textPosition === 'auto';
// var hasInside = hasBoth || textPosition === 'inside';
// var hasOutside = hasBoth || textPosition === 'outside';
// if(hasInside || hasOutside) {
// var textFont = coerceFont(coerce, 'textfont', layout.font);
// if(hasInside) coerceFont(coerce, 'insidetextfont', textFont);
// if(hasOutside) coerceFont(coerce, 'outsidetextfont', textFont);
// coerce('constraintext');
// coerce('selected.textfont.color');
// coerce('unselected.textfont.color');
// coerce('cliponaxis');
// }
handleStyleDefaults(traceIn, traceOut, coerce, defaultColor, layout);
Lib.coerceSelectionMarkerOpacity(traceOut, coerce);
};
},{"../../lib":503,"../bar/style_defaults":663,"../scatterpolar/defaults":1001,"./attributes":665}],668:[function(_dereq_,module,exports){
'use strict';
var Fx = _dereq_('../../components/fx');
var Lib = _dereq_('../../lib');
var getTraceColor = _dereq_('../bar/hover').getTraceColor;
var fillText = Lib.fillText;
var makeHoverPointText = _dereq_('../scatterpolar/hover').makeHoverPointText;
var isPtInsidePolygon = _dereq_('../../plots/polar/helpers').isPtInsidePolygon;
module.exports = function hoverPoints(pointData, xval, yval) {
var cd = pointData.cd;
var trace = cd[0].trace;
var subplot = pointData.subplot;
var radialAxis = subplot.radialAxis;
var angularAxis = subplot.angularAxis;
var vangles = subplot.vangles;
var inboxFn = vangles ? isPtInsidePolygon : Lib.isPtInsideSector;
var maxHoverDistance = pointData.maxHoverDistance;
var period = angularAxis._period || 2 * Math.PI;
var rVal = Math.abs(radialAxis.g2p(Math.sqrt(xval * xval + yval * yval)));
var thetaVal = Math.atan2(yval, xval);
// polar.(x|y)axis.p2c doesn't get the reversed radial axis range case right
if(radialAxis.range[0] > radialAxis.range[1]) {
thetaVal += Math.PI;
}
var distFn = function(di) {
if(inboxFn(rVal, thetaVal, [di.rp0, di.rp1], [di.thetag0, di.thetag1], vangles)) {
return maxHoverDistance +
// add a little to the pseudo-distance for wider bars, so that like scatter,
// if you are over two overlapping bars, the narrower one wins.
Math.min(1, Math.abs(di.thetag1 - di.thetag0) / period) - 1 +
// add a gradient so hovering near the end of a
// bar makes it a little closer match
(di.rp1 - rVal) / (di.rp1 - di.rp0) - 1;
} else {
return Infinity;
}
};
Fx.getClosest(cd, distFn, pointData);
if(pointData.index === false) return;
var index = pointData.index;
var cdi = cd[index];
pointData.x0 = pointData.x1 = cdi.ct[0];
pointData.y0 = pointData.y1 = cdi.ct[1];
var _cdi = Lib.extendFlat({}, cdi, {r: cdi.s, theta: cdi.p});
fillText(cdi, trace, pointData);
makeHoverPointText(_cdi, trace, subplot, pointData);
pointData.hovertemplate = trace.hovertemplate;
pointData.color = getTraceColor(trace, cdi);
pointData.xLabelVal = pointData.yLabelVal = undefined;
if(cdi.s < 0) {
pointData.idealAlign = 'left';
}
return [pointData];
};
},{"../../components/fx":406,"../../lib":503,"../../plots/polar/helpers":621,"../bar/hover":655,"../scatterpolar/hover":1003}],669:[function(_dereq_,module,exports){
'use strict';
module.exports = {
moduleType: 'trace',
name: 'barpolar',
basePlotModule: _dereq_('../../plots/polar'),
categories: ['polar', 'bar', 'showLegend'],
attributes: _dereq_('./attributes'),
layoutAttributes: _dereq_('./layout_attributes'),
supplyDefaults: _dereq_('./defaults'),
supplyLayoutDefaults: _dereq_('./layout_defaults'),
calc: _dereq_('./calc').calc,
crossTraceCalc: _dereq_('./calc').crossTraceCalc,
plot: _dereq_('./plot'),
colorbar: _dereq_('../scatter/marker_colorbar'),
formatLabels: _dereq_('../scatterpolar/format_labels'),
style: _dereq_('../bar/style').style,
styleOnSelect: _dereq_('../bar/style').styleOnSelect,
hoverPoints: _dereq_('./hover'),
selectPoints: _dereq_('../bar/select'),
meta: {
}
};
},{"../../plots/polar":622,"../bar/select":660,"../bar/style":662,"../scatter/marker_colorbar":943,"../scatterpolar/format_labels":1002,"./attributes":665,"./calc":666,"./defaults":667,"./hover":668,"./layout_attributes":670,"./layout_defaults":671,"./plot":672}],670:[function(_dereq_,module,exports){
'use strict';
module.exports = {
barmode: {
valType: 'enumerated',
values: ['stack', 'overlay'],
dflt: 'stack',
editType: 'calc',
},
bargap: {
valType: 'number',
dflt: 0.1,
min: 0,
max: 1,
editType: 'calc',
}
};
},{}],671:[function(_dereq_,module,exports){
'use strict';
var Lib = _dereq_('../../lib');
var attrs = _dereq_('./layout_attributes');
module.exports = function(layoutIn, layoutOut, fullData) {
var subplotsDone = {};
var sp;
function coerce(attr, dflt) {
return Lib.coerce(layoutIn[sp] || {}, layoutOut[sp], attrs, attr, dflt);
}
for(var i = 0; i < fullData.length; i++) {
var trace = fullData[i];
if(trace.type === 'barpolar' && trace.visible === true) {
sp = trace.subplot;
if(!subplotsDone[sp]) {
coerce('barmode');
coerce('bargap');
subplotsDone[sp] = 1;
}
}
}
};
},{"../../lib":503,"./layout_attributes":670}],672:[function(_dereq_,module,exports){
'use strict';
var d3 = _dereq_('@plotly/d3');
var isNumeric = _dereq_('fast-isnumeric');
var Lib = _dereq_('../../lib');
var Drawing = _dereq_('../../components/drawing');
var helpers = _dereq_('../../plots/polar/helpers');
module.exports = function plot(gd, subplot, cdbar) {
var xa = subplot.xaxis;
var ya = subplot.yaxis;
var radialAxis = subplot.radialAxis;
var angularAxis = subplot.angularAxis;
var pathFn = makePathFn(subplot);
var barLayer = subplot.layers.frontplot.select('g.barlayer');
Lib.makeTraceGroups(barLayer, cdbar, 'trace bars').each(function() {
var plotGroup = d3.select(this);
var pointGroup = Lib.ensureSingle(plotGroup, 'g', 'points');
var bars = pointGroup.selectAll('g.point').data(Lib.identity);
bars.enter().append('g')
.style('vector-effect', 'non-scaling-stroke')
.style('stroke-miterlimit', 2)
.classed('point', true);
bars.exit().remove();
bars.each(function(di) {
var bar = d3.select(this);
var rp0 = di.rp0 = radialAxis.c2p(di.s0);
var rp1 = di.rp1 = radialAxis.c2p(di.s1);
var thetag0 = di.thetag0 = angularAxis.c2g(di.p0);
var thetag1 = di.thetag1 = angularAxis.c2g(di.p1);
var dPath;
if(!isNumeric(rp0) || !isNumeric(rp1) ||
!isNumeric(thetag0) || !isNumeric(thetag1) ||
rp0 === rp1 || thetag0 === thetag1
) {
// do not remove blank bars, to keep data-to-node
// mapping intact during radial drag, that we
// can skip calling _module.style during interactions
dPath = 'M0,0Z';
} else {
// this 'center' pt is used for selections and hover labels
var rg1 = radialAxis.c2g(di.s1);
var thetagMid = (thetag0 + thetag1) / 2;
di.ct = [
xa.c2p(rg1 * Math.cos(thetagMid)),
ya.c2p(rg1 * Math.sin(thetagMid))
];
dPath = pathFn(rp0, rp1, thetag0, thetag1);
}
Lib.ensureSingle(bar, 'path').attr('d', dPath);
});
// clip plotGroup, when trace layer isn't clipped
Drawing.setClipUrl(
plotGroup,
subplot._hasClipOnAxisFalse ? subplot.clipIds.forTraces : null,
gd
);
});
};
function makePathFn(subplot) {
var cxx = subplot.cxx;
var cyy = subplot.cyy;
if(subplot.vangles) {
return function(r0, r1, _a0, _a1) {
var a0, a1;
if(Lib.angleDelta(_a0, _a1) > 0) {
a0 = _a0;
a1 = _a1;
} else {
a0 = _a1;
a1 = _a0;
}
var va0 = helpers.findEnclosingVertexAngles(a0, subplot.vangles)[0];
var va1 = helpers.findEnclosingVertexAngles(a1, subplot.vangles)[1];
var vaBar = [va0, (a0 + a1) / 2, va1];
return helpers.pathPolygonAnnulus(r0, r1, a0, a1, vaBar, cxx, cyy);
};
}
return function(r0, r1, a0, a1) {
return Lib.pathAnnulus(r0, r1, a0, a1, cxx, cyy);
};
}
},{"../../components/drawing":388,"../../lib":503,"../../plots/polar/helpers":621,"@plotly/d3":58,"fast-isnumeric":190}],673:[function(_dereq_,module,exports){
'use strict';
var scatterAttrs = _dereq_('../scatter/attributes');
var barAttrs = _dereq_('../bar/attributes');
var colorAttrs = _dereq_('../../components/color/attributes');
var axisHoverFormat = _dereq_('../../plots/cartesian/axis_format_attributes').axisHoverFormat;
var hovertemplateAttrs = _dereq_('../../plots/template_attributes').hovertemplateAttrs;
var extendFlat = _dereq_('../../lib/extend').extendFlat;
var scatterMarkerAttrs = scatterAttrs.marker;
var scatterMarkerLineAttrs = scatterMarkerAttrs.line;
module.exports = {
y: {
valType: 'data_array',
editType: 'calc+clearAxisTypes',
},
x: {
valType: 'data_array',
editType: 'calc+clearAxisTypes',
},
x0: {
valType: 'any',
editType: 'calc+clearAxisTypes',
},
y0: {
valType: 'any',
editType: 'calc+clearAxisTypes',
},
dx: {
valType: 'number',
editType: 'calc',
},
dy: {
valType: 'number',
editType: 'calc',
},
xperiod: scatterAttrs.xperiod,
yperiod: scatterAttrs.yperiod,
xperiod0: scatterAttrs.xperiod0,
yperiod0: scatterAttrs.yperiod0,
xperiodalignment: scatterAttrs.xperiodalignment,
yperiodalignment: scatterAttrs.yperiodalignment,
xhoverformat: axisHoverFormat('x'),
yhoverformat: axisHoverFormat('y'),
name: {
valType: 'string',
editType: 'calc+clearAxisTypes',
},
q1: {
valType: 'data_array',
editType: 'calc+clearAxisTypes',
},
median: {
valType: 'data_array',
editType: 'calc+clearAxisTypes',
},
q3: {
valType: 'data_array',
editType: 'calc+clearAxisTypes',
},
lowerfence: {
valType: 'data_array',
editType: 'calc',
},
upperfence: {
valType: 'data_array',
editType: 'calc',
},
notched: {
valType: 'boolean',
editType: 'calc',
},
notchwidth: {
valType: 'number',
min: 0,
max: 0.5,
dflt: 0.25,
editType: 'calc',
},
notchspan: {
valType: 'data_array',
editType: 'calc',
},
// TODO
// maybe add
// - loweroutlierbound / upperoutlierbound
// - lowersuspectedoutlierbound / uppersuspectedoutlierbound
boxpoints: {
valType: 'enumerated',
values: ['all', 'outliers', 'suspectedoutliers', false],
editType: 'calc',
},
jitter: {
valType: 'number',
min: 0,
max: 1,
editType: 'calc',
},
pointpos: {
valType: 'number',
min: -2,
max: 2,
editType: 'calc',
},
boxmean: {
valType: 'enumerated',
values: [true, 'sd', false],
editType: 'calc',
},
mean: {
valType: 'data_array',
editType: 'calc',
},
sd: {
valType: 'data_array',
editType: 'calc',
},
orientation: {
valType: 'enumerated',
values: ['v', 'h'],
editType: 'calc+clearAxisTypes',
},
quartilemethod: {
valType: 'enumerated',
values: ['linear', 'exclusive', 'inclusive'],
dflt: 'linear',
editType: 'calc',
},
width: {
valType: 'number',
min: 0,
dflt: 0,
editType: 'calc',
},
marker: {
outliercolor: {
valType: 'color',
dflt: 'rgba(0, 0, 0, 0)',
editType: 'style',
},
symbol: extendFlat({}, scatterMarkerAttrs.symbol,
{arrayOk: false, editType: 'plot'}),
opacity: extendFlat({}, scatterMarkerAttrs.opacity,
{arrayOk: false, dflt: 1, editType: 'style'}),
size: extendFlat({}, scatterMarkerAttrs.size,
{arrayOk: false, editType: 'calc'}),
color: extendFlat({}, scatterMarkerAttrs.color,
{arrayOk: false, editType: 'style'}),
line: {
color: extendFlat({}, scatterMarkerLineAttrs.color,
{arrayOk: false, dflt: colorAttrs.defaultLine, editType: 'style'}
),
width: extendFlat({}, scatterMarkerLineAttrs.width,
{arrayOk: false, dflt: 0, editType: 'style'}
),
outliercolor: {
valType: 'color',
editType: 'style',
},
outlierwidth: {
valType: 'number',
min: 0,
dflt: 1,
editType: 'style',
},
editType: 'style'
},
editType: 'plot'
},
line: {
color: {
valType: 'color',
editType: 'style',
},
width: {
valType: 'number',
min: 0,
dflt: 2,
editType: 'style',
},
editType: 'plot'
},
fillcolor: scatterAttrs.fillcolor,
whiskerwidth: {
valType: 'number',
min: 0,
max: 1,
dflt: 0.5,
editType: 'calc',
},
offsetgroup: barAttrs.offsetgroup,
alignmentgroup: barAttrs.alignmentgroup,
selected: {
marker: scatterAttrs.selected.marker,
editType: 'style'
},
unselected: {
marker: scatterAttrs.unselected.marker,
editType: 'style'
},
text: extendFlat({}, scatterAttrs.text, {
}),
hovertext: extendFlat({}, scatterAttrs.hovertext, {
}),
hovertemplate: hovertemplateAttrs({
}),
hoveron: {
valType: 'flaglist',
flags: ['boxes', 'points'],
dflt: 'boxes+points',
editType: 'style',
}
};
},{"../../components/color/attributes":365,"../../lib/extend":493,"../../plots/cartesian/axis_format_attributes":557,"../../plots/template_attributes":633,"../bar/attributes":648,"../scatter/attributes":925}],674:[function(_dereq_,module,exports){
'use strict';
var isNumeric = _dereq_('fast-isnumeric');
var Axes = _dereq_('../../plots/cartesian/axes');
var alignPeriod = _dereq_('../../plots/cartesian/align_period');
var Lib = _dereq_('../../lib');
var BADNUM = _dereq_('../../constants/numerical').BADNUM;
var _ = Lib._;
module.exports = function calc(gd, trace) {
var fullLayout = gd._fullLayout;
var xa = Axes.getFromId(gd, trace.xaxis || 'x');
var ya = Axes.getFromId(gd, trace.yaxis || 'y');
var cd = [];
// N.B. violin reuses same Box.calc
var numKey = trace.type === 'violin' ? '_numViolins' : '_numBoxes';
var i, j;
var valAxis, valLetter;
var posAxis, posLetter;
var hasPeriod;
if(trace.orientation === 'h') {
valAxis = xa;
valLetter = 'x';
posAxis = ya;
posLetter = 'y';
hasPeriod = !!trace.yperiodalignment;
} else {
valAxis = ya;
valLetter = 'y';
posAxis = xa;
posLetter = 'x';
hasPeriod = !!trace.xperiodalignment;
}
var allPosArrays = getPosArrays(trace, posLetter, posAxis, fullLayout[numKey]);
var posArray = allPosArrays[0];
var origPos = allPosArrays[1];
var dv = Lib.distinctVals(posArray, posAxis);
var posDistinct = dv.vals;
var dPos = dv.minDiff / 2;
// item in trace calcdata
var cdi;
// array of {v: v, i, i} sample pts
var pts;
// values of the `pts` array of objects
var boxVals;
// length of sample
var N;
// single sample point
var pt;
// single sample value
var v;
// filter function for outlier pts
// outlier definition based on http://www.physics.csbsju.edu/stats/box2.html
var ptFilterFn = (trace.boxpoints || trace.points) === 'all' ?
Lib.identity :
function(pt) { return (pt.v < cdi.lf || pt.v > cdi.uf); };
if(trace._hasPreCompStats) {
var valArrayRaw = trace[valLetter];
var d2c = function(k) { return valAxis.d2c((trace[k] || [])[i]); };
var minVal = Infinity;
var maxVal = -Infinity;
for(i = 0; i < trace._length; i++) {
var posi = posArray[i];
if(!isNumeric(posi)) continue;
cdi = {};
cdi.pos = cdi[posLetter] = posi;
if(hasPeriod && origPos) {
cdi.orig_p = origPos[i]; // used by hover
}
cdi.q1 = d2c('q1');
cdi.med = d2c('median');
cdi.q3 = d2c('q3');
pts = [];
if(valArrayRaw && Lib.isArrayOrTypedArray(valArrayRaw[i])) {
for(j = 0; j < valArrayRaw[i].length; j++) {
v = valAxis.d2c(valArrayRaw[i][j]);
if(v !== BADNUM) {
pt = {v: v, i: [i, j]};
arraysToCalcdata(pt, trace, [i, j]);
pts.push(pt);
}
}
}
cdi.pts = pts.sort(sortByVal);
boxVals = cdi[valLetter] = pts.map(extractVal);
N = boxVals.length;
if(cdi.med !== BADNUM && cdi.q1 !== BADNUM && cdi.q3 !== BADNUM &&
cdi.med >= cdi.q1 && cdi.q3 >= cdi.med
) {
var lf = d2c('lowerfence');
cdi.lf = (lf !== BADNUM && lf <= cdi.q1) ?
lf :
computeLowerFence(cdi, boxVals, N);
var uf = d2c('upperfence');
cdi.uf = (uf !== BADNUM && uf >= cdi.q3) ?
uf :
computeUpperFence(cdi, boxVals, N);
var mean = d2c('mean');
cdi.mean = (mean !== BADNUM) ?
mean :
(N ? Lib.mean(boxVals, N) : (cdi.q1 + cdi.q3) / 2);
var sd = d2c('sd');
cdi.sd = (mean !== BADNUM && sd >= 0) ?
sd :
(N ? Lib.stdev(boxVals, N, cdi.mean) : (cdi.q3 - cdi.q1));
cdi.lo = computeLowerOutlierBound(cdi);
cdi.uo = computeUpperOutlierBound(cdi);
var ns = d2c('notchspan');
ns = (ns !== BADNUM && ns > 0) ? ns : computeNotchSpan(cdi, N);
cdi.ln = cdi.med - ns;
cdi.un = cdi.med + ns;
var imin = cdi.lf;
var imax = cdi.uf;
if(trace.boxpoints && boxVals.length) {
imin = Math.min(imin, boxVals[0]);
imax = Math.max(imax, boxVals[N - 1]);
}
if(trace.notched) {
imin = Math.min(imin, cdi.ln);
imax = Math.max(imax, cdi.un);
}
cdi.min = imin;
cdi.max = imax;
} else {
Lib.warn([
'Invalid input - make sure that q1 <= median <= q3',
'q1 = ' + cdi.q1,
'median = ' + cdi.med,
'q3 = ' + cdi.q3
].join('\n'));
var v0;
if(cdi.med !== BADNUM) {
v0 = cdi.med;
} else if(cdi.q1 !== BADNUM) {
if(cdi.q3 !== BADNUM) v0 = (cdi.q1 + cdi.q3) / 2;
else v0 = cdi.q1;
} else if(cdi.q3 !== BADNUM) {
v0 = cdi.q3;
} else {
v0 = 0;
}
// draw box as line segment
cdi.med = v0;
cdi.q1 = cdi.q3 = v0;
cdi.lf = cdi.uf = v0;
cdi.mean = cdi.sd = v0;
cdi.ln = cdi.un = v0;
cdi.min = cdi.max = v0;
}
minVal = Math.min(minVal, cdi.min);
maxVal = Math.max(maxVal, cdi.max);
cdi.pts2 = pts.filter(ptFilterFn);
cd.push(cdi);
}
trace._extremes[valAxis._id] = Axes.findExtremes(valAxis,
[minVal, maxVal],
{padded: true}
);
} else {
var valArray = valAxis.makeCalcdata(trace, valLetter);
var posBins = makeBins(posDistinct, dPos);
var pLen = posDistinct.length;
var ptsPerBin = initNestedArray(pLen);
// bin pts info per position bins
for(i = 0; i < trace._length; i++) {
v = valArray[i];
if(!isNumeric(v)) continue;
var n = Lib.findBin(posArray[i], posBins);
if(n >= 0 && n < pLen) {
pt = {v: v, i: i};
arraysToCalcdata(pt, trace, i);
ptsPerBin[n].push(pt);
}
}
var minLowerNotch = Infinity;
var maxUpperNotch = -Infinity;
var quartilemethod = trace.quartilemethod;
var usesExclusive = quartilemethod === 'exclusive';
var usesInclusive = quartilemethod === 'inclusive';
// build calcdata trace items, one item per distinct position
for(i = 0; i < pLen; i++) {
if(ptsPerBin[i].length > 0) {
cdi = {};
cdi.pos = cdi[posLetter] = posDistinct[i];
pts = cdi.pts = ptsPerBin[i].sort(sortByVal);
boxVals = cdi[valLetter] = pts.map(extractVal);
N = boxVals.length;
cdi.min = boxVals[0];
cdi.max = boxVals[N - 1];
cdi.mean = Lib.mean(boxVals, N);
cdi.sd = Lib.stdev(boxVals, N, cdi.mean);
cdi.med = Lib.interp(boxVals, 0.5);
if((N % 2) && (usesExclusive || usesInclusive)) {
var lower;
var upper;
if(usesExclusive) {
// do NOT include the median in either half
lower = boxVals.slice(0, N / 2);
upper = boxVals.slice(N / 2 + 1);
} else if(usesInclusive) {
// include the median in either half
lower = boxVals.slice(0, N / 2 + 1);
upper = boxVals.slice(N / 2);
}
cdi.q1 = Lib.interp(lower, 0.5);
cdi.q3 = Lib.interp(upper, 0.5);
} else {
cdi.q1 = Lib.interp(boxVals, 0.25);
cdi.q3 = Lib.interp(boxVals, 0.75);
}
// lower and upper fences
cdi.lf = computeLowerFence(cdi, boxVals, N);
cdi.uf = computeUpperFence(cdi, boxVals, N);
// lower and upper outliers bounds
cdi.lo = computeLowerOutlierBound(cdi);
cdi.uo = computeUpperOutlierBound(cdi);
// lower and upper notches
var mci = computeNotchSpan(cdi, N);
cdi.ln = cdi.med - mci;
cdi.un = cdi.med + mci;
minLowerNotch = Math.min(minLowerNotch, cdi.ln);
maxUpperNotch = Math.max(maxUpperNotch, cdi.un);
cdi.pts2 = pts.filter(ptFilterFn);
cd.push(cdi);
}
}
trace._extremes[valAxis._id] = Axes.findExtremes(valAxis,
trace.notched ? valArray.concat([minLowerNotch, maxUpperNotch]) : valArray,
{padded: true}
);
}
calcSelection(cd, trace);
if(cd.length > 0) {
cd[0].t = {
num: fullLayout[numKey],
dPos: dPos,
posLetter: posLetter,
valLetter: valLetter,
labels: {
med: _(gd, 'median:'),
min: _(gd, 'min:'),
q1: _(gd, 'q1:'),
q3: _(gd, 'q3:'),
max: _(gd, 'max:'),
mean: trace.boxmean === 'sd' ? _(gd, 'mean ± σ:') : _(gd, 'mean:'),
lf: _(gd, 'lower fence:'),
uf: _(gd, 'upper fence:')
}
};
fullLayout[numKey]++;
return cd;
} else {
return [{t: {empty: true}}];
}
};
// In vertical (horizontal) box plots:
// if no x (y) data, use x0 (y0), or name
// so if you want one box
// per trace, set x0 (y0) to the x (y) value or category for this trace
// (or set x (y) to a constant array matching y (x))
function getPosArrays(trace, posLetter, posAxis, num) {
var hasPosArray = posLetter in trace;
var hasPos0 = posLetter + '0' in trace;
var hasPosStep = 'd' + posLetter in trace;
if(hasPosArray || (hasPos0 && hasPosStep)) {
var origPos = posAxis.makeCalcdata(trace, posLetter);
var pos = alignPeriod(trace, posAxis, posLetter, origPos).vals;
return [pos, origPos];
}
var pos0;
if(hasPos0) {
pos0 = trace[posLetter + '0'];
} else if('name' in trace && (
posAxis.type === 'category' || (
isNumeric(trace.name) &&
['linear', 'log'].indexOf(posAxis.type) !== -1
) || (
Lib.isDateTime(trace.name) &&
posAxis.type === 'date'
)
)) {
pos0 = trace.name;
} else {
pos0 = num;
}
var pos0c = posAxis.type === 'multicategory' ?
posAxis.r2c_just_indices(pos0) :
posAxis.d2c(pos0, 0, trace[posLetter + 'calendar']);
var len = trace._length;
var out = new Array(len);
for(var i = 0; i < len; i++) out[i] = pos0c;
return [out];
}
function makeBins(x, dx) {
var len = x.length;
var bins = new Array(len + 1);
for(var i = 0; i < len; i++) {
bins[i] = x[i] - dx;
}
bins[len] = x[len - 1] + dx;
return bins;
}
function initNestedArray(len) {
var arr = new Array(len);
for(var i = 0; i < len; i++) {
arr[i] = [];
}
return arr;
}
var TRACE_TO_CALC = {
text: 'tx',
hovertext: 'htx'
};
function arraysToCalcdata(pt, trace, ptNumber) {
for(var k in TRACE_TO_CALC) {
if(Lib.isArrayOrTypedArray(trace[k])) {
if(Array.isArray(ptNumber)) {
if(Lib.isArrayOrTypedArray(trace[k][ptNumber[0]])) {
pt[TRACE_TO_CALC[k]] = trace[k][ptNumber[0]][ptNumber[1]];
}
} else {
pt[TRACE_TO_CALC[k]] = trace[k][ptNumber];
}
}
}
}
function calcSelection(cd, trace) {
if(Lib.isArrayOrTypedArray(trace.selectedpoints)) {
for(var i = 0; i < cd.length; i++) {
var pts = cd[i].pts || [];
var ptNumber2cdIndex = {};
for(var j = 0; j < pts.length; j++) {
ptNumber2cdIndex[pts[j].i] = j;
}
Lib.tagSelected(pts, trace, ptNumber2cdIndex);
}
}
}
function sortByVal(a, b) { return a.v - b.v; }
function extractVal(o) { return o.v; }
// last point below 1.5 * IQR
function computeLowerFence(cdi, boxVals, N) {
if(N === 0) return cdi.q1;
return Math.min(
cdi.q1,
boxVals[Math.min(
Lib.findBin(2.5 * cdi.q1 - 1.5 * cdi.q3, boxVals, true) + 1,
N - 1
)]
);
}
// last point above 1.5 * IQR
function computeUpperFence(cdi, boxVals, N) {
if(N === 0) return cdi.q3;
return Math.max(
cdi.q3,
boxVals[Math.max(
Lib.findBin(2.5 * cdi.q3 - 1.5 * cdi.q1, boxVals),
0
)]
);
}
// 3 IQR below (don't clip to max/min,
// this is only for discriminating suspected & far outliers)
function computeLowerOutlierBound(cdi) {
return 4 * cdi.q1 - 3 * cdi.q3;
}
// 3 IQR above (don't clip to max/min,
// this is only for discriminating suspected & far outliers)
function computeUpperOutlierBound(cdi) {
return 4 * cdi.q3 - 3 * cdi.q1;
}
// 95% confidence intervals for median
function computeNotchSpan(cdi, N) {
if(N === 0) return 0;
return 1.57 * (cdi.q3 - cdi.q1) / Math.sqrt(N);
}
},{"../../constants/numerical":479,"../../lib":503,"../../plots/cartesian/align_period":551,"../../plots/cartesian/axes":554,"fast-isnumeric":190}],675:[function(_dereq_,module,exports){
'use strict';
var Axes = _dereq_('../../plots/cartesian/axes');
var Lib = _dereq_('../../lib');
var getAxisGroup = _dereq_('../../plots/cartesian/constraints').getAxisGroup;
var orientations = ['v', 'h'];
function crossTraceCalc(gd, plotinfo) {
var calcdata = gd.calcdata;
var xa = plotinfo.xaxis;
var ya = plotinfo.yaxis;
for(var i = 0; i < orientations.length; i++) {
var orientation = orientations[i];
var posAxis = orientation === 'h' ? ya : xa;
var boxList = [];
// make list of boxes / candlesticks
// For backward compatibility, candlesticks are treated as if they *are* box traces here
for(var j = 0; j < calcdata.length; j++) {
var cd = calcdata[j];
var t = cd[0].t;
var trace = cd[0].trace;
if(trace.visible === true &&
(trace.type === 'box' || trace.type === 'candlestick') &&
!t.empty &&
(trace.orientation || 'v') === orientation &&
trace.xaxis === xa._id &&
trace.yaxis === ya._id
) {
boxList.push(j);
}
}
setPositionOffset('box', gd, boxList, posAxis);
}
}
function setPositionOffset(traceType, gd, boxList, posAxis) {
var calcdata = gd.calcdata;
var fullLayout = gd._fullLayout;
var axId = posAxis._id;
var axLetter = axId.charAt(0);
var i, j, calcTrace;
var pointList = [];
var shownPts = 0;
// make list of box points
for(i = 0; i < boxList.length; i++) {
calcTrace = calcdata[boxList[i]];
for(j = 0; j < calcTrace.length; j++) {
pointList.push(posAxis.c2l(calcTrace[j].pos, true));
shownPts += (calcTrace[j].pts2 || []).length;
}
}
if(!pointList.length) return;
// box plots - update dPos based on multiple traces
var boxdv = Lib.distinctVals(pointList);
if(posAxis.type === 'category' || posAxis.type === 'multicategory') {
boxdv.minDiff = 1;
}
var dPos0 = boxdv.minDiff / 2;
// check for forced minimum dtick
Axes.minDtick(posAxis, boxdv.minDiff, boxdv.vals[0], true);
var numKey = traceType === 'violin' ? '_numViolins' : '_numBoxes';
var numTotal = fullLayout[numKey];
var group = fullLayout[traceType + 'mode'] === 'group' && numTotal > 1;
var groupFraction = 1 - fullLayout[traceType + 'gap'];
var groupGapFraction = 1 - fullLayout[traceType + 'groupgap'];
for(i = 0; i < boxList.length; i++) {
calcTrace = calcdata[boxList[i]];
var trace = calcTrace[0].trace;
var t = calcTrace[0].t;
var width = trace.width;
var side = trace.side;
// position coordinate delta
var dPos;
// box half width;
var bdPos;
// box center offset
var bPos;
// half-width within which to accept hover for this box/violin
// always split the distance to the closest box/violin
var wHover;
if(width) {
dPos = bdPos = wHover = width / 2;
bPos = 0;
} else {
dPos = dPos0;
if(group) {
var groupId = getAxisGroup(fullLayout, posAxis._id) + trace.orientation;
var alignmentGroups = fullLayout._alignmentOpts[groupId] || {};
var alignmentGroupOpts = alignmentGroups[trace.alignmentgroup] || {};
var nOffsetGroups = Object.keys(alignmentGroupOpts.offsetGroups || {}).length;
var num = nOffsetGroups || numTotal;
var shift = nOffsetGroups ? trace._offsetIndex : t.num;
bdPos = dPos * groupFraction * groupGapFraction / num;
bPos = 2 * dPos * (-0.5 + (shift + 0.5) / num) * groupFraction;
wHover = dPos * groupFraction / num;
} else {
bdPos = dPos * groupFraction * groupGapFraction;
bPos = 0;
wHover = dPos;
}
}
t.dPos = dPos;
t.bPos = bPos;
t.bdPos = bdPos;
t.wHover = wHover;
// box/violin-only value-space push value
var pushplus;
var pushminus;
// edge of box/violin
var edge = bPos + bdPos;
var edgeplus;
var edgeminus;
// value-space padding
var vpadplus;
var vpadminus;
// pixel-space padding
var ppadplus;
var ppadminus;
// do we add 5% of both sides (more logic for points beyond box/violin below)
var padded = Boolean(width);
// does this trace show points?
var hasPts = (trace.boxpoints || trace.points) && (shownPts > 0);
if(side === 'positive') {
pushplus = dPos * (width ? 1 : 0.5);
edgeplus = edge;
pushminus = edgeplus = bPos;
} else if(side === 'negative') {
pushplus = edgeplus = bPos;
pushminus = dPos * (width ? 1 : 0.5);
edgeminus = edge;
} else {
pushplus = pushminus = dPos;
edgeplus = edgeminus = edge;
}
if(hasPts) {
var pointpos = trace.pointpos;
var jitter = trace.jitter;
var ms = trace.marker.size / 2;
var pp = 0;
if((pointpos + jitter) >= 0) {
pp = edge * (pointpos + jitter);
if(pp > pushplus) {
// (++) beyond plus-value, use pp
padded = true;
ppadplus = ms;
vpadplus = pp;
} else if(pp > edgeplus) {
// (+), use push-value (it's bigger), but add px-pad
ppadplus = ms;
vpadplus = pushplus;
}
}
if(pp <= pushplus) {
// (->) fallback to push value
vpadplus = pushplus;
}
var pm = 0;
if((pointpos - jitter) <= 0) {
pm = -edge * (pointpos - jitter);
if(pm > pushminus) {
// (--) beyond plus-value, use pp
padded = true;
ppadminus = ms;
vpadminus = pm;
} else if(pm > edgeminus) {
// (-), use push-value (it's bigger), but add px-pad
ppadminus = ms;
vpadminus = pushminus;
}
}
if(pm <= pushminus) {
// (<-) fallback to push value
vpadminus = pushminus;
}
} else {
vpadplus = pushplus;
vpadminus = pushminus;
}
var pos = new Array(calcTrace.length);
for(j = 0; j < calcTrace.length; j++) {
pos[j] = calcTrace[j].pos;
}
trace._extremes[axId] = Axes.findExtremes(posAxis, pos, {
padded: padded,
vpadminus: vpadminus,
vpadplus: vpadplus,
vpadLinearized: true,
// N.B. SVG px-space positive/negative
ppadminus: {x: ppadminus, y: ppadplus}[axLetter],
ppadplus: {x: ppadplus, y: ppadminus}[axLetter],
});
}
}
module.exports = {
crossTraceCalc: crossTraceCalc,
setPositionOffset: setPositionOffset
};
},{"../../lib":503,"../../plots/cartesian/axes":554,"../../plots/cartesian/constraints":562}],676:[function(_dereq_,module,exports){
'use strict';
var Lib = _dereq_('../../lib');
var Registry = _dereq_('../../registry');
var Color = _dereq_('../../components/color');
var handlePeriodDefaults = _dereq_('../scatter/period_defaults');
var handleGroupingDefaults = _dereq_('../bar/defaults').handleGroupingDefaults;
var autoType = _dereq_('../../plots/cartesian/axis_autotype');
var attributes = _dereq_('./attributes');
function supplyDefaults(traceIn, traceOut, defaultColor, layout) {
function coerce(attr, dflt) {
return Lib.coerce(traceIn, traceOut, attributes, attr, dflt);
}
handleSampleDefaults(traceIn, traceOut, coerce, layout);
if(traceOut.visible === false) return;
handlePeriodDefaults(traceIn, traceOut, layout, coerce);
coerce('xhoverformat');
coerce('yhoverformat');
var hasPreCompStats = traceOut._hasPreCompStats;
if(hasPreCompStats) {
coerce('lowerfence');
coerce('upperfence');
}
coerce('line.color', (traceIn.marker || {}).color || defaultColor);
coerce('line.width');
coerce('fillcolor', Color.addOpacity(traceOut.line.color, 0.5));
var boxmeanDflt = false;
if(hasPreCompStats) {
var mean = coerce('mean');
var sd = coerce('sd');
if(mean && mean.length) {
boxmeanDflt = true;
if(sd && sd.length) boxmeanDflt = 'sd';
}
}
coerce('boxmean', boxmeanDflt);
coerce('whiskerwidth');
coerce('width');
coerce('quartilemethod');
var notchedDflt = false;
if(hasPreCompStats) {
var notchspan = coerce('notchspan');
if(notchspan && notchspan.length) {
notchedDflt = true;
}
} else if(Lib.validate(traceIn.notchwidth, attributes.notchwidth)) {
notchedDflt = true;
}
var notched = coerce('notched', notchedDflt);
if(notched) coerce('notchwidth');
handlePointsDefaults(traceIn, traceOut, coerce, {prefix: 'box'});
}
function handleSampleDefaults(traceIn, traceOut, coerce, layout) {
function getDims(arr) {
var dims = 0;
if(arr && arr.length) {
dims += 1;
if(Lib.isArrayOrTypedArray(arr[0]) && arr[0].length) {
dims += 1;
}
}
return dims;
}
function valid(astr) {
return Lib.validate(traceIn[astr], attributes[astr]);
}
var y = coerce('y');
var x = coerce('x');
var sLen;
if(traceOut.type === 'box') {
var q1 = coerce('q1');
var median = coerce('median');
var q3 = coerce('q3');
traceOut._hasPreCompStats = (
q1 && q1.length &&
median && median.length &&
q3 && q3.length
);
sLen = Math.min(
Lib.minRowLength(q1),
Lib.minRowLength(median),
Lib.minRowLength(q3)
);
}
var yDims = getDims(y);
var xDims = getDims(x);
var yLen = yDims && Lib.minRowLength(y);
var xLen = xDims && Lib.minRowLength(x);
var calendar = layout.calendar;
var opts = {
autotypenumbers: layout.autotypenumbers
};
var defaultOrientation, len;
if(traceOut._hasPreCompStats) {
switch(String(xDims) + String(yDims)) {
// no x / no y
case '00':
var setInX = valid('x0') || valid('dx');
var setInY = valid('y0') || valid('dy');
if(setInY && !setInX) {
defaultOrientation = 'h';
} else {
defaultOrientation = 'v';
}
len = sLen;
break;
// just x
case '10':
defaultOrientation = 'v';
len = Math.min(sLen, xLen);
break;
case '20':
defaultOrientation = 'h';
len = Math.min(sLen, x.length);
break;
// just y
case '01':
defaultOrientation = 'h';
len = Math.min(sLen, yLen);
break;
case '02':
defaultOrientation = 'v';
len = Math.min(sLen, y.length);
break;
// both
case '12':
defaultOrientation = 'v';
len = Math.min(sLen, xLen, y.length);
break;
case '21':
defaultOrientation = 'h';
len = Math.min(sLen, x.length, yLen);
break;
case '11':
// this one is ill-defined
len = 0;
break;
case '22':
var hasCategories = false;
var i;
for(i = 0; i < x.length; i++) {
if(autoType(x[i], calendar, opts) === 'category') {
hasCategories = true;
break;
}
}
if(hasCategories) {
defaultOrientation = 'v';
len = Math.min(sLen, xLen, y.length);
} else {
for(i = 0; i < y.length; i++) {
if(autoType(y[i], calendar, opts) === 'category') {
hasCategories = true;
break;
}
}
if(hasCategories) {
defaultOrientation = 'h';
len = Math.min(sLen, x.length, yLen);
} else {
defaultOrientation = 'v';
len = Math.min(sLen, xLen, y.length);
}
}
break;
}
} else if(yDims > 0) {
defaultOrientation = 'v';
if(xDims > 0) {
len = Math.min(xLen, yLen);
} else {
len = Math.min(yLen);
}
} else if(xDims > 0) {
defaultOrientation = 'h';
len = Math.min(xLen);
} else {
len = 0;
}
if(!len) {
traceOut.visible = false;
return;
}
traceOut._length = len;
var orientation = coerce('orientation', defaultOrientation);
// these are just used for positioning, they never define the sample
if(traceOut._hasPreCompStats) {
if(orientation === 'v' && xDims === 0) {
coerce('x0', 0);
coerce('dx', 1);
} else if(orientation === 'h' && yDims === 0) {
coerce('y0', 0);
coerce('dy', 1);
}
} else {
if(orientation === 'v' && xDims === 0) {
coerce('x0');
} else if(orientation === 'h' && yDims === 0) {
coerce('y0');
}
}
var handleCalendarDefaults = Registry.getComponentMethod('calendars', 'handleTraceDefaults');
handleCalendarDefaults(traceIn, traceOut, ['x', 'y'], layout);
}
function handlePointsDefaults(traceIn, traceOut, coerce, opts) {
var prefix = opts.prefix;
var outlierColorDflt = Lib.coerce2(traceIn, traceOut, attributes, 'marker.outliercolor');
var lineoutliercolor = coerce('marker.line.outliercolor');
var modeDflt = 'outliers';
if(traceOut._hasPreCompStats) {
modeDflt = 'all';
} else if(outlierColorDflt || lineoutliercolor) {
modeDflt = 'suspectedoutliers';
}
var mode = coerce(prefix + 'points', modeDflt);
if(mode) {
coerce('jitter', mode === 'all' ? 0.3 : 0);
coerce('pointpos', mode === 'all' ? -1.5 : 0);
coerce('marker.symbol');
coerce('marker.opacity');
coerce('marker.size');
coerce('marker.color', traceOut.line.color);
coerce('marker.line.color');
coerce('marker.line.width');
if(mode === 'suspectedoutliers') {
coerce('marker.line.outliercolor', traceOut.marker.color);
coerce('marker.line.outlierwidth');
}
coerce('selected.marker.color');
coerce('unselected.marker.color');
coerce('selected.marker.size');
coerce('unselected.marker.size');
coerce('text');
coerce('hovertext');
} else {
delete traceOut.marker;
}
var hoveron = coerce('hoveron');
if(hoveron === 'all' || hoveron.indexOf('points') !== -1) {
coerce('hovertemplate');
}
Lib.coerceSelectionMarkerOpacity(traceOut, coerce);
}
function crossTraceDefaults(fullData, fullLayout) {
var traceIn, traceOut;
function coerce(attr) {
return Lib.coerce(traceOut._input, traceOut, attributes, attr);
}
for(var i = 0; i < fullData.length; i++) {
traceOut = fullData[i];
var traceType = traceOut.type;
if(traceType === 'box' || traceType === 'violin') {
traceIn = traceOut._input;
if(fullLayout[traceType + 'mode'] === 'group') {
handleGroupingDefaults(traceIn, traceOut, fullLayout, coerce);
}
}
}
}
module.exports = {
supplyDefaults: supplyDefaults,
crossTraceDefaults: crossTraceDefaults,
handleSampleDefaults: handleSampleDefaults,
handlePointsDefaults: handlePointsDefaults
};
},{"../../components/color":366,"../../lib":503,"../../plots/cartesian/axis_autotype":555,"../../registry":638,"../bar/defaults":652,"../scatter/period_defaults":945,"./attributes":673}],677:[function(_dereq_,module,exports){
'use strict';
module.exports = function eventData(out, pt) {
// Note: hoverOnBox property is needed for click-to-select
// to ignore when a box was clicked. This is the reason box
// implements this custom eventData function.
if(pt.hoverOnBox) out.hoverOnBox = pt.hoverOnBox;
if('xVal' in pt) out.x = pt.xVal;
if('yVal' in pt) out.y = pt.yVal;
if(pt.xa) out.xaxis = pt.xa;
if(pt.ya) out.yaxis = pt.ya;
return out;
};
},{}],678:[function(_dereq_,module,exports){
'use strict';
var Axes = _dereq_('../../plots/cartesian/axes');
var Lib = _dereq_('../../lib');
var Fx = _dereq_('../../components/fx');
var Color = _dereq_('../../components/color');
var fillText = Lib.fillText;
function hoverPoints(pointData, xval, yval, hovermode) {
var cd = pointData.cd;
var trace = cd[0].trace;
var hoveron = trace.hoveron;
var closeBoxData = [];
var closePtData;
if(hoveron.indexOf('boxes') !== -1) {
closeBoxData = closeBoxData.concat(hoverOnBoxes(pointData, xval, yval, hovermode));
}
if(hoveron.indexOf('points') !== -1) {
closePtData = hoverOnPoints(pointData, xval, yval);
}
// If there's a point in range and hoveron has points, show the best single point only.
// If hoveron has boxes and there's no point in range (or hoveron doesn't have points), show the box stats.
if(hovermode === 'closest') {
if(closePtData) return [closePtData];
return closeBoxData;
}
// Otherwise in compare mode, allow a point AND the box stats to be labeled
// If there are multiple boxes in range (ie boxmode = 'overlay') we'll see stats for all of them.
if(closePtData) {
closeBoxData.push(closePtData);
return closeBoxData;
}
return closeBoxData;
}
function hoverOnBoxes(pointData, xval, yval, hovermode) {
var cd = pointData.cd;
var xa = pointData.xa;
var ya = pointData.ya;
var trace = cd[0].trace;
var t = cd[0].t;
var isViolin = trace.type === 'violin';
var closeBoxData = [];
var pLetter, vLetter, pAxis, vAxis, vVal, pVal, dx, dy, dPos,
hoverPseudoDistance, spikePseudoDistance;
var boxDelta = t.bdPos;
var boxDeltaPos, boxDeltaNeg;
var posAcceptance = t.wHover;
var shiftPos = function(di) { return pAxis.c2l(di.pos) + t.bPos - pAxis.c2l(pVal); };
if(isViolin && trace.side !== 'both') {
if(trace.side === 'positive') {
dPos = function(di) {
var pos = shiftPos(di);
return Fx.inbox(pos, pos + posAcceptance, hoverPseudoDistance);
};
boxDeltaPos = boxDelta;
boxDeltaNeg = 0;
}
if(trace.side === 'negative') {
dPos = function(di) {
var pos = shiftPos(di);
return Fx.inbox(pos - posAcceptance, pos, hoverPseudoDistance);
};
boxDeltaPos = 0;
boxDeltaNeg = boxDelta;
}
} else {
dPos = function(di) {
var pos = shiftPos(di);
return Fx.inbox(pos - posAcceptance, pos + posAcceptance, hoverPseudoDistance);
};
boxDeltaPos = boxDeltaNeg = boxDelta;
}
var dVal;
if(isViolin) {
dVal = function(di) {
return Fx.inbox(di.span[0] - vVal, di.span[1] - vVal, hoverPseudoDistance);
};
} else {
dVal = function(di) {
return Fx.inbox(di.min - vVal, di.max - vVal, hoverPseudoDistance);
};
}
if(trace.orientation === 'h') {
vVal = xval;
pVal = yval;
dx = dVal;
dy = dPos;
pLetter = 'y';
pAxis = ya;
vLetter = 'x';
vAxis = xa;
} else {
vVal = yval;
pVal = xval;
dx = dPos;
dy = dVal;
pLetter = 'x';
pAxis = xa;
vLetter = 'y';
vAxis = ya;
}
// if two boxes are overlaying, let the narrowest one win
var pseudoDistance = Math.min(1, boxDelta / Math.abs(pAxis.r2c(pAxis.range[1]) - pAxis.r2c(pAxis.range[0])));
hoverPseudoDistance = pointData.maxHoverDistance - pseudoDistance;
spikePseudoDistance = pointData.maxSpikeDistance - pseudoDistance;
function dxy(di) { return (dx(di) + dy(di)) / 2; }
var distfn = Fx.getDistanceFunction(hovermode, dx, dy, dxy);
Fx.getClosest(cd, distfn, pointData);
// skip the rest (for this trace) if we didn't find a close point
// and create the item(s) in closedata for this point
if(pointData.index === false) return [];
var di = cd[pointData.index];
var lc = trace.line.color;
var mc = (trace.marker || {}).color;
if(Color.opacity(lc) && trace.line.width) pointData.color = lc;
else if(Color.opacity(mc) && trace.boxpoints) pointData.color = mc;
else pointData.color = trace.fillcolor;
pointData[pLetter + '0'] = pAxis.c2p(di.pos + t.bPos - boxDeltaNeg, true);
pointData[pLetter + '1'] = pAxis.c2p(di.pos + t.bPos + boxDeltaPos, true);
pointData[pLetter + 'LabelVal'] = di.orig_p !== undefined ? di.orig_p : di.pos;
var spikePosAttr = pLetter + 'Spike';
pointData.spikeDistance = dxy(di) * spikePseudoDistance / hoverPseudoDistance;
pointData[spikePosAttr] = pAxis.c2p(di.pos, true);
// box plots: each "point" gets many labels
var usedVals = {};
var attrs = ['med', 'q1', 'q3', 'min', 'max'];
if(trace.boxmean || (trace.meanline || {}).visible) {
attrs.push('mean');
}
if(trace.boxpoints || trace.points) {
attrs.push('lf', 'uf');
}
for(var i = 0; i < attrs.length; i++) {
var attr = attrs[i];
if(!(attr in di) || (di[attr] in usedVals)) continue;
usedVals[di[attr]] = true;
// copy out to a new object for each value to label
var val = di[attr];
var valPx = vAxis.c2p(val, true);
var pointData2 = Lib.extendFlat({}, pointData);
pointData2.attr = attr;
pointData2[vLetter + '0'] = pointData2[vLetter + '1'] = valPx;
pointData2[vLetter + 'LabelVal'] = val;
pointData2[vLetter + 'Label'] = (t.labels ? t.labels[attr] + ' ' : '') + Axes.hoverLabelText(vAxis, val, trace[vLetter + 'hoverformat']);
// Note: introduced to be able to distinguish a
// clicked point from a box during click-to-select
pointData2.hoverOnBox = true;
if(attr === 'mean' && ('sd' in di) && trace.boxmean === 'sd') {
pointData2[vLetter + 'err'] = di.sd;
}
// only keep name and spikes on the first item (median)
pointData.name = '';
pointData.spikeDistance = undefined;
pointData[spikePosAttr] = undefined;
// no hovertemplate support yet
pointData2.hovertemplate = false;
closeBoxData.push(pointData2);
}
return closeBoxData;
}
function hoverOnPoints(pointData, xval, yval) {
var cd = pointData.cd;
var xa = pointData.xa;
var ya = pointData.ya;
var trace = cd[0].trace;
var xPx = xa.c2p(xval);
var yPx = ya.c2p(yval);
var closePtData;
var dx = function(di) {
var rad = Math.max(3, di.mrc || 0);
return Math.max(Math.abs(xa.c2p(di.x) - xPx) - rad, 1 - 3 / rad);
};
var dy = function(di) {
var rad = Math.max(3, di.mrc || 0);
return Math.max(Math.abs(ya.c2p(di.y) - yPx) - rad, 1 - 3 / rad);
};
var distfn = Fx.quadrature(dx, dy);
// show one point per trace
var ijClosest = false;
var di, pt;
for(var i = 0; i < cd.length; i++) {
di = cd[i];
for(var j = 0; j < (di.pts || []).length; j++) {
pt = di.pts[j];
var newDistance = distfn(pt);
if(newDistance <= pointData.distance) {
pointData.distance = newDistance;
ijClosest = [i, j];
}
}
}
if(!ijClosest) return false;
di = cd[ijClosest[0]];
pt = di.pts[ijClosest[1]];
var xc = xa.c2p(pt.x, true);
var yc = ya.c2p(pt.y, true);
var rad = pt.mrc || 1;
closePtData = Lib.extendFlat({}, pointData, {
// corresponds to index in x/y input data array
index: pt.i,
color: (trace.marker || {}).color,
name: trace.name,
x0: xc - rad,
x1: xc + rad,
y0: yc - rad,
y1: yc + rad,
spikeDistance: pointData.distance,
hovertemplate: trace.hovertemplate
});
var origPos = di.orig_p;
var pos = origPos !== undefined ? origPos : di.pos;
var pa;
if(trace.orientation === 'h') {
pa = ya;
closePtData.xLabelVal = pt.x;
closePtData.yLabelVal = pos;
} else {
pa = xa;
closePtData.xLabelVal = pos;
closePtData.yLabelVal = pt.y;
}
var pLetter = pa._id.charAt(0);
closePtData[pLetter + 'Spike'] = pa.c2p(di.pos, true);
fillText(pt, trace, closePtData);
return closePtData;
}
module.exports = {
hoverPoints: hoverPoints,
hoverOnBoxes: hoverOnBoxes,
hoverOnPoints: hoverOnPoints
};
},{"../../components/color":366,"../../components/fx":406,"../../lib":503,"../../plots/cartesian/axes":554}],679:[function(_dereq_,module,exports){
'use strict';
module.exports = {
attributes: _dereq_('./attributes'),
layoutAttributes: _dereq_('./layout_attributes'),
supplyDefaults: _dereq_('./defaults').supplyDefaults,
crossTraceDefaults: _dereq_('./defaults').crossTraceDefaults,
supplyLayoutDefaults: _dereq_('./layout_defaults').supplyLayoutDefaults,
calc: _dereq_('./calc'),
crossTraceCalc: _dereq_('./cross_trace_calc').crossTraceCalc,
plot: _dereq_('./plot').plot,
style: _dereq_('./style').style,
styleOnSelect: _dereq_('./style').styleOnSelect,
hoverPoints: _dereq_('./hover').hoverPoints,
eventData: _dereq_('./event_data'),
selectPoints: _dereq_('./select'),
moduleType: 'trace',
name: 'box',
basePlotModule: _dereq_('../../plots/cartesian'),
categories: ['cartesian', 'svg', 'symbols', 'oriented', 'box-violin', 'showLegend', 'boxLayout', 'zoomScale'],
meta: {
}
};
},{"../../plots/cartesian":568,"./attributes":673,"./calc":674,"./cross_trace_calc":675,"./defaults":676,"./event_data":677,"./hover":678,"./layout_attributes":680,"./layout_defaults":681,"./plot":682,"./select":683,"./style":684}],680:[function(_dereq_,module,exports){
'use strict';
module.exports = {
boxmode: {
valType: 'enumerated',
values: ['group', 'overlay'],
dflt: 'overlay',
editType: 'calc',
},
boxgap: {
valType: 'number',
min: 0,
max: 1,
dflt: 0.3,
editType: 'calc',
},
boxgroupgap: {
valType: 'number',
min: 0,
max: 1,
dflt: 0.3,
editType: 'calc',
}
};
},{}],681:[function(_dereq_,module,exports){
'use strict';
var Registry = _dereq_('../../registry');
var Lib = _dereq_('../../lib');
var layoutAttributes = _dereq_('./layout_attributes');
function _supply(layoutIn, layoutOut, fullData, coerce, traceType) {
var category = traceType + 'Layout';
var hasTraceType = false;
for(var i = 0; i < fullData.length; i++) {
var trace = fullData[i];
if(Registry.traceIs(trace, category)) {
hasTraceType = true;
break;
}
}
if(!hasTraceType) return;
coerce(traceType + 'mode');
coerce(traceType + 'gap');
coerce(traceType + 'groupgap');
}
function supplyLayoutDefaults(layoutIn, layoutOut, fullData) {
function coerce(attr, dflt) {
return Lib.coerce(layoutIn, layoutOut, layoutAttributes, attr, dflt);
}
_supply(layoutIn, layoutOut, fullData, coerce, 'box');
}
module.exports = {
supplyLayoutDefaults: supplyLayoutDefaults,
_supply: _supply
};
},{"../../lib":503,"../../registry":638,"./layout_attributes":680}],682:[function(_dereq_,module,exports){
'use strict';
var d3 = _dereq_('@plotly/d3');
var Lib = _dereq_('../../lib');
var Drawing = _dereq_('../../components/drawing');
// constants for dynamic jitter (ie less jitter for sparser points)
var JITTERCOUNT = 5; // points either side of this to include
var JITTERSPREAD = 0.01; // fraction of IQR to count as "dense"
function plot(gd, plotinfo, cdbox, boxLayer) {
var xa = plotinfo.xaxis;
var ya = plotinfo.yaxis;
Lib.makeTraceGroups(boxLayer, cdbox, 'trace boxes').each(function(cd) {
var plotGroup = d3.select(this);
var cd0 = cd[0];
var t = cd0.t;
var trace = cd0.trace;
// whisker width
t.wdPos = t.bdPos * trace.whiskerwidth;
if(trace.visible !== true || t.empty) {
plotGroup.remove();
return;
}
var posAxis, valAxis;
if(trace.orientation === 'h') {
posAxis = ya;
valAxis = xa;
} else {
posAxis = xa;
valAxis = ya;
}
plotBoxAndWhiskers(plotGroup, {pos: posAxis, val: valAxis}, trace, t);
plotPoints(plotGroup, {x: xa, y: ya}, trace, t);
plotBoxMean(plotGroup, {pos: posAxis, val: valAxis}, trace, t);
});
}
function plotBoxAndWhiskers(sel, axes, trace, t) {
var isHorizontal = trace.orientation === 'h';
var valAxis = axes.val;
var posAxis = axes.pos;
var posHasRangeBreaks = !!posAxis.rangebreaks;
var bPos = t.bPos;
var wdPos = t.wdPos || 0;
var bPosPxOffset = t.bPosPxOffset || 0;
var whiskerWidth = trace.whiskerwidth || 0;
var notched = trace.notched || false;
var nw = notched ? 1 - 2 * trace.notchwidth : 1;
// to support for one-sided box
var bdPos0;
var bdPos1;
if(Array.isArray(t.bdPos)) {
bdPos0 = t.bdPos[0];
bdPos1 = t.bdPos[1];
} else {
bdPos0 = t.bdPos;
bdPos1 = t.bdPos;
}
var paths = sel.selectAll('path.box').data((
trace.type !== 'violin' ||
trace.box.visible
) ? Lib.identity : []);
paths.enter().append('path')
.style('vector-effect', 'non-scaling-stroke')
.attr('class', 'box');
paths.exit().remove();
paths.each(function(d) {
if(d.empty) return 'M0,0Z';
var lcenter = posAxis.c2l(d.pos + bPos, true);
var pos0 = posAxis.l2p(lcenter - bdPos0) + bPosPxOffset;
var pos1 = posAxis.l2p(lcenter + bdPos1) + bPosPxOffset;
var posc = posHasRangeBreaks ? (pos0 + pos1) / 2 : posAxis.l2p(lcenter) + bPosPxOffset;
var r = trace.whiskerwidth;
var posw0 = posHasRangeBreaks ? pos0 * r + (1 - r) * posc : posAxis.l2p(lcenter - wdPos) + bPosPxOffset;
var posw1 = posHasRangeBreaks ? pos1 * r + (1 - r) * posc : posAxis.l2p(lcenter + wdPos) + bPosPxOffset;
var posm0 = posAxis.l2p(lcenter - bdPos0 * nw) + bPosPxOffset;
var posm1 = posAxis.l2p(lcenter + bdPos1 * nw) + bPosPxOffset;
var q1 = valAxis.c2p(d.q1, true);
var q3 = valAxis.c2p(d.q3, true);
// make sure median isn't identical to either of the
// quartiles, so we can see it
var m = Lib.constrain(
valAxis.c2p(d.med, true),
Math.min(q1, q3) + 1, Math.max(q1, q3) - 1
);
// for compatibility with box, violin, and candlestick
// perhaps we should put this into cd0.t instead so it's more explicit,
// but what we have now is:
// - box always has d.lf, but boxpoints can be anything
// - violin has d.lf and should always use it (boxpoints is undefined)
// - candlestick has only min/max
var useExtremes = (d.lf === undefined) || (trace.boxpoints === false);
var lf = valAxis.c2p(useExtremes ? d.min : d.lf, true);
var uf = valAxis.c2p(useExtremes ? d.max : d.uf, true);
var ln = valAxis.c2p(d.ln, true);
var un = valAxis.c2p(d.un, true);
if(isHorizontal) {
d3.select(this).attr('d',
'M' + m + ',' + posm0 + 'V' + posm1 + // median line
'M' + q1 + ',' + pos0 + 'V' + pos1 + // left edge
(notched ?
'H' + ln + 'L' + m + ',' + posm1 + 'L' + un + ',' + pos1 :
''
) + // top notched edge
'H' + q3 + // end of the top edge
'V' + pos0 + // right edge
(notched ? 'H' + un + 'L' + m + ',' + posm0 + 'L' + ln + ',' + pos0 : '') + // bottom notched edge
'Z' + // end of the box
'M' + q1 + ',' + posc + 'H' + lf + 'M' + q3 + ',' + posc + 'H' + uf + // whiskers
(whiskerWidth === 0 ?
'' : // whisker caps
'M' + lf + ',' + posw0 + 'V' + posw1 + 'M' + uf + ',' + posw0 + 'V' + posw1
)
);
} else {
d3.select(this).attr('d',
'M' + posm0 + ',' + m + 'H' + posm1 + // median line
'M' + pos0 + ',' + q1 + 'H' + pos1 + // top of the box
(notched ?
'V' + ln + 'L' + posm1 + ',' + m + 'L' + pos1 + ',' + un :
''
) + // notched right edge
'V' + q3 + // end of the right edge
'H' + pos0 + // bottom of the box
(notched ?
'V' + un + 'L' + posm0 + ',' + m + 'L' + pos0 + ',' + ln :
''
) + // notched left edge
'Z' + // end of the box
'M' + posc + ',' + q1 + 'V' + lf + 'M' + posc + ',' + q3 + 'V' + uf + // whiskers
(whiskerWidth === 0 ?
'' : // whisker caps
'M' + posw0 + ',' + lf + 'H' + posw1 + 'M' + posw0 + ',' + uf + 'H' + posw1
)
);
}
});
}
function plotPoints(sel, axes, trace, t) {
var xa = axes.x;
var ya = axes.y;
var bdPos = t.bdPos;
var bPos = t.bPos;
// to support violin points
var mode = trace.boxpoints || trace.points;
// repeatable pseudo-random number generator
Lib.seedPseudoRandom();
// since box plot points get an extra level of nesting, each
// box needs the trace styling info
var fn = function(d) {
d.forEach(function(v) {
v.t = t;
v.trace = trace;
});
return d;
};
var gPoints = sel.selectAll('g.points')
.data(mode ? fn : []);
gPoints.enter().append('g')
.attr('class', 'points');
gPoints.exit().remove();
var paths = gPoints.selectAll('path')
.data(function(d) {
var i;
var pts = d.pts2;
// normally use IQR, but if this is 0 or too small, use max-min
var typicalSpread = Math.max((d.max - d.min) / 10, d.q3 - d.q1);
var minSpread = typicalSpread * 1e-9;
var spreadLimit = typicalSpread * JITTERSPREAD;
var jitterFactors = [];
var maxJitterFactor = 0;
var newJitter;
// dynamic jitter
if(trace.jitter) {
if(typicalSpread === 0) {
// edge case of no spread at all: fall back to max jitter
maxJitterFactor = 1;
jitterFactors = new Array(pts.length);
for(i = 0; i < pts.length; i++) {
jitterFactors[i] = 1;
}
} else {
for(i = 0; i < pts.length; i++) {
var i0 = Math.max(0, i - JITTERCOUNT);
var pmin = pts[i0].v;
var i1 = Math.min(pts.length - 1, i + JITTERCOUNT);
var pmax = pts[i1].v;
if(mode !== 'all') {
if(pts[i].v < d.lf) pmax = Math.min(pmax, d.lf);
else pmin = Math.max(pmin, d.uf);
}
var jitterFactor = Math.sqrt(spreadLimit * (i1 - i0) / (pmax - pmin + minSpread)) || 0;
jitterFactor = Lib.constrain(Math.abs(jitterFactor), 0, 1);
jitterFactors.push(jitterFactor);
maxJitterFactor = Math.max(jitterFactor, maxJitterFactor);
}
}
newJitter = trace.jitter * 2 / (maxJitterFactor || 1);
}
// fills in 'x' and 'y' in calcdata 'pts' item
for(i = 0; i < pts.length; i++) {
var pt = pts[i];
var v = pt.v;
var jitterOffset = trace.jitter ?
(newJitter * jitterFactors[i] * (Lib.pseudoRandom() - 0.5)) :
0;
var posPx = d.pos + bPos + bdPos * (trace.pointpos + jitterOffset);
if(trace.orientation === 'h') {
pt.y = posPx;
pt.x = v;
} else {
pt.x = posPx;
pt.y = v;
}
// tag suspected outliers
if(mode === 'suspectedoutliers' && v < d.uo && v > d.lo) {
pt.so = true;
}
}
return pts;
});
paths.enter().append('path')
.classed('point', true);
paths.exit().remove();
paths.call(Drawing.translatePoints, xa, ya);
}
function plotBoxMean(sel, axes, trace, t) {
var valAxis = axes.val;
var posAxis = axes.pos;
var posHasRangeBreaks = !!posAxis.rangebreaks;
var bPos = t.bPos;
var bPosPxOffset = t.bPosPxOffset || 0;
// to support violin mean lines
var mode = trace.boxmean || (trace.meanline || {}).visible;
// to support for one-sided box
var bdPos0;
var bdPos1;
if(Array.isArray(t.bdPos)) {
bdPos0 = t.bdPos[0];
bdPos1 = t.bdPos[1];
} else {
bdPos0 = t.bdPos;
bdPos1 = t.bdPos;
}
var paths = sel.selectAll('path.mean').data((
(trace.type === 'box' && trace.boxmean) ||
(trace.type === 'violin' && trace.box.visible && trace.meanline.visible)
) ? Lib.identity : []);
paths.enter().append('path')
.attr('class', 'mean')
.style({
fill: 'none',
'vector-effect': 'non-scaling-stroke'
});
paths.exit().remove();
paths.each(function(d) {
var lcenter = posAxis.c2l(d.pos + bPos, true);
var pos0 = posAxis.l2p(lcenter - bdPos0) + bPosPxOffset;
var pos1 = posAxis.l2p(lcenter + bdPos1) + bPosPxOffset;
var posc = posHasRangeBreaks ? (pos0 + pos1) / 2 : posAxis.l2p(lcenter) + bPosPxOffset;
var m = valAxis.c2p(d.mean, true);
var sl = valAxis.c2p(d.mean - d.sd, true);
var sh = valAxis.c2p(d.mean + d.sd, true);
if(trace.orientation === 'h') {
d3.select(this).attr('d',
'M' + m + ',' + pos0 + 'V' + pos1 +
(mode === 'sd' ?
'm0,0L' + sl + ',' + posc + 'L' + m + ',' + pos0 + 'L' + sh + ',' + posc + 'Z' :
'')
);
} else {
d3.select(this).attr('d',
'M' + pos0 + ',' + m + 'H' + pos1 +
(mode === 'sd' ?
'm0,0L' + posc + ',' + sl + 'L' + pos0 + ',' + m + 'L' + posc + ',' + sh + 'Z' :
'')
);
}
});
}
module.exports = {
plot: plot,
plotBoxAndWhiskers: plotBoxAndWhiskers,
plotPoints: plotPoints,
plotBoxMean: plotBoxMean
};
},{"../../components/drawing":388,"../../lib":503,"@plotly/d3":58}],683:[function(_dereq_,module,exports){
'use strict';
module.exports = function selectPoints(searchInfo, selectionTester) {
var cd = searchInfo.cd;
var xa = searchInfo.xaxis;
var ya = searchInfo.yaxis;
var selection = [];
var i, j;
if(selectionTester === false) {
for(i = 0; i < cd.length; i++) {
for(j = 0; j < (cd[i].pts || []).length; j++) {
// clear selection
cd[i].pts[j].selected = 0;
}
}
} else {
for(i = 0; i < cd.length; i++) {
for(j = 0; j < (cd[i].pts || []).length; j++) {
var pt = cd[i].pts[j];
var x = xa.c2p(pt.x);
var y = ya.c2p(pt.y);
if(selectionTester.contains([x, y], null, pt.i, searchInfo)) {
selection.push({
pointNumber: pt.i,
x: xa.c2d(pt.x),
y: ya.c2d(pt.y)
});
pt.selected = 1;
} else {
pt.selected = 0;
}
}
}
}
return selection;
};
},{}],684:[function(_dereq_,module,exports){
'use strict';
var d3 = _dereq_('@plotly/d3');
var Color = _dereq_('../../components/color');
var Drawing = _dereq_('../../components/drawing');
function style(gd, cd, sel) {
var s = sel ? sel : d3.select(gd).selectAll('g.trace.boxes');
s.style('opacity', function(d) { return d[0].trace.opacity; });
s.each(function(d) {
var el = d3.select(this);
var trace = d[0].trace;
var lineWidth = trace.line.width;
function styleBox(boxSel, lineWidth, lineColor, fillColor) {
boxSel.style('stroke-width', lineWidth + 'px')
.call(Color.stroke, lineColor)
.call(Color.fill, fillColor);
}
var allBoxes = el.selectAll('path.box');
if(trace.type === 'candlestick') {
allBoxes.each(function(boxData) {
if(boxData.empty) return;
var thisBox = d3.select(this);
var container = trace[boxData.dir]; // dir = 'increasing' or 'decreasing'
styleBox(thisBox, container.line.width, container.line.color, container.fillcolor);
// TODO: custom selection style for candlesticks
thisBox.style('opacity', trace.selectedpoints && !boxData.selected ? 0.3 : 1);
});
} else {
styleBox(allBoxes, lineWidth, trace.line.color, trace.fillcolor);
el.selectAll('path.mean')
.style({
'stroke-width': lineWidth,
'stroke-dasharray': (2 * lineWidth) + 'px,' + lineWidth + 'px'
})
.call(Color.stroke, trace.line.color);
var pts = el.selectAll('path.point');
Drawing.pointStyle(pts, trace, gd);
}
});
}
function styleOnSelect(gd, cd, sel) {
var trace = cd[0].trace;
var pts = sel.selectAll('path.point');
if(trace.selectedpoints) {
Drawing.selectedPointStyle(pts, trace);
} else {
Drawing.pointStyle(pts, trace, gd);
}
}
module.exports = {
style: style,
styleOnSelect: styleOnSelect
};
},{"../../components/color":366,"../../components/drawing":388,"@plotly/d3":58}],685:[function(_dereq_,module,exports){
'use strict';
var extendFlat = _dereq_('../../lib').extendFlat;
var axisHoverFormat = _dereq_('../../plots/cartesian/axis_format_attributes').axisHoverFormat;
var OHLCattrs = _dereq_('../ohlc/attributes');
var boxAttrs = _dereq_('../box/attributes');
function directionAttrs(lineColorDefault) {
return {
line: {
color: extendFlat({}, boxAttrs.line.color, {dflt: lineColorDefault}),
width: boxAttrs.line.width,
editType: 'style'
},
fillcolor: boxAttrs.fillcolor,
editType: 'style'
};
}
module.exports = {
xperiod: OHLCattrs.xperiod,
xperiod0: OHLCattrs.xperiod0,
xperiodalignment: OHLCattrs.xperiodalignment,
xhoverformat: axisHoverFormat('x'),
yhoverformat: axisHoverFormat('y'),
x: OHLCattrs.x,
open: OHLCattrs.open,
high: OHLCattrs.high,
low: OHLCattrs.low,
close: OHLCattrs.close,
line: {
width: extendFlat({}, boxAttrs.line.width, {
}),
editType: 'style'
},
increasing: directionAttrs(OHLCattrs.increasing.line.color.dflt),
decreasing: directionAttrs(OHLCattrs.decreasing.line.color.dflt),
text: OHLCattrs.text,
hovertext: OHLCattrs.hovertext,
whiskerwidth: extendFlat({}, boxAttrs.whiskerwidth, { dflt: 0 }),
hoverlabel: OHLCattrs.hoverlabel,
};
},{"../../lib":503,"../../plots/cartesian/axis_format_attributes":557,"../box/attributes":673,"../ohlc/attributes":871}],686:[function(_dereq_,module,exports){
'use strict';
var Lib = _dereq_('../../lib');
var Axes = _dereq_('../../plots/cartesian/axes');
var alignPeriod = _dereq_('../../plots/cartesian/align_period');
var calcCommon = _dereq_('../ohlc/calc').calcCommon;
module.exports = function(gd, trace) {
var fullLayout = gd._fullLayout;
var xa = Axes.getFromId(gd, trace.xaxis);
var ya = Axes.getFromId(gd, trace.yaxis);
var origX = xa.makeCalcdata(trace, 'x');
var x = alignPeriod(trace, xa, 'x', origX).vals;
var cd = calcCommon(gd, trace, origX, x, ya, ptFunc);
if(cd.length) {
Lib.extendFlat(cd[0].t, {
num: fullLayout._numBoxes,
dPos: Lib.distinctVals(x).minDiff / 2,
posLetter: 'x',
valLetter: 'y',
});
fullLayout._numBoxes++;
return cd;
} else {
return [{t: {empty: true}}];
}
};
function ptFunc(o, h, l, c) {
return {
min: l,
q1: Math.min(o, c),
med: c,
q3: Math.max(o, c),
max: h,
};
}
},{"../../lib":503,"../../plots/cartesian/align_period":551,"../../plots/cartesian/axes":554,"../ohlc/calc":872}],687:[function(_dereq_,module,exports){
'use strict';
var Lib = _dereq_('../../lib');
var Color = _dereq_('../../components/color');
var handleOHLC = _dereq_('../ohlc/ohlc_defaults');
var handlePeriodDefaults = _dereq_('../scatter/period_defaults');
var attributes = _dereq_('./attributes');
module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout) {
function coerce(attr, dflt) {
return Lib.coerce(traceIn, traceOut, attributes, attr, dflt);
}
var len = handleOHLC(traceIn, traceOut, coerce, layout);
if(!len) {
traceOut.visible = false;
return;
}
handlePeriodDefaults(traceIn, traceOut, layout, coerce, {x: true});
coerce('xhoverformat');
coerce('yhoverformat');
coerce('line.width');
handleDirection(traceIn, traceOut, coerce, 'increasing');
handleDirection(traceIn, traceOut, coerce, 'decreasing');
coerce('text');
coerce('hovertext');
coerce('whiskerwidth');
layout._requestRangeslider[traceOut.xaxis] = true;
};
function handleDirection(traceIn, traceOut, coerce, direction) {
var lineColor = coerce(direction + '.line.color');
coerce(direction + '.line.width', traceOut.line.width);
coerce(direction + '.fillcolor', Color.addOpacity(lineColor, 0.5));
}
},{"../../components/color":366,"../../lib":503,"../ohlc/ohlc_defaults":876,"../scatter/period_defaults":945,"./attributes":685}],688:[function(_dereq_,module,exports){
'use strict';
module.exports = {
moduleType: 'trace',
name: 'candlestick',
basePlotModule: _dereq_('../../plots/cartesian'),
categories: ['cartesian', 'svg', 'showLegend', 'candlestick', 'boxLayout'],
meta: {
},
attributes: _dereq_('./attributes'),
layoutAttributes: _dereq_('../box/layout_attributes'),
supplyLayoutDefaults: _dereq_('../box/layout_defaults').supplyLayoutDefaults,
crossTraceCalc: _dereq_('../box/cross_trace_calc').crossTraceCalc,
supplyDefaults: _dereq_('./defaults'),
calc: _dereq_('./calc'),
plot: _dereq_('../box/plot').plot,
layerName: 'boxlayer',
style: _dereq_('../box/style').style,
hoverPoints: _dereq_('../ohlc/hover').hoverPoints,
selectPoints: _dereq_('../ohlc/select')
};
},{"../../plots/cartesian":568,"../box/cross_trace_calc":675,"../box/layout_attributes":680,"../box/layout_defaults":681,"../box/plot":682,"../box/style":684,"../ohlc/hover":874,"../ohlc/select":878,"./attributes":685,"./calc":686,"./defaults":687}],689:[function(_dereq_,module,exports){
'use strict';
var handleAxisDefaults = _dereq_('./axis_defaults');
var Template = _dereq_('../../plot_api/plot_template');
module.exports = function handleABDefaults(traceIn, traceOut, fullLayout, coerce, dfltColor) {
var a = coerce('a');
if(!a) {
coerce('da');
coerce('a0');
}
var b = coerce('b');
if(!b) {
coerce('db');
coerce('b0');
}
mimickAxisDefaults(traceIn, traceOut, fullLayout, dfltColor);
};
function mimickAxisDefaults(traceIn, traceOut, fullLayout, dfltColor) {
var axesList = ['aaxis', 'baxis'];
axesList.forEach(function(axName) {
var axLetter = axName.charAt(0);
var axIn = traceIn[axName] || {};
var axOut = Template.newContainer(traceOut, axName);
var defaultOptions = {
tickfont: 'x',
id: axLetter + 'axis',
letter: axLetter,
font: traceOut.font,
name: axName,
data: traceIn[axLetter],
calendar: traceOut.calendar,
dfltColor: dfltColor,
bgColor: fullLayout.paper_bgcolor,
autotypenumbersDflt: fullLayout.autotypenumbers,
fullLayout: fullLayout
};
handleAxisDefaults(axIn, axOut, defaultOptions);
axOut._categories = axOut._categories || [];
// so we don't have to repeat autotype unnecessarily,
// copy an autotype back to traceIn
if(!traceIn[axName] && axIn.type !== '-') {
traceIn[axName] = {type: axIn.type};
}
});
}
},{"../../plot_api/plot_template":543,"./axis_defaults":694}],690:[function(_dereq_,module,exports){
'use strict';
var isArrayOrTypedArray = _dereq_('../../lib').isArrayOrTypedArray;
module.exports = function(a) {
return minMax(a, 0);
};
function minMax(a, depth) {
// Limit to ten dimensional datasets. This seems *exceedingly* unlikely to
// ever cause problems or even be a concern. It's include strictly so that
// circular arrays could never cause this to loop.
if(!isArrayOrTypedArray(a) || depth >= 10) {
return null;
}
var min = Infinity;
var max = -Infinity;
var n = a.length;
for(var i = 0; i < n; i++) {
var datum = a[i];
if(isArrayOrTypedArray(datum)) {
var result = minMax(datum, depth + 1);
if(result) {
min = Math.min(result[0], min);
max = Math.max(result[1], max);
}
} else {
min = Math.min(datum, min);
max = Math.max(datum, max);
}
}
return [min, max];
}
},{"../../lib":503}],691:[function(_dereq_,module,exports){
'use strict';
var fontAttrs = _dereq_('../../plots/font_attributes');
var axisAttrs = _dereq_('./axis_attributes');
var colorAttrs = _dereq_('../../components/color/attributes');
var carpetFont = fontAttrs({
editType: 'calc',
});
// TODO: inherit from global font
carpetFont.family.dflt = '"Open Sans", verdana, arial, sans-serif';
carpetFont.size.dflt = 12;
carpetFont.color.dflt = colorAttrs.defaultLine;
module.exports = {
carpet: {
valType: 'string',
editType: 'calc',
},
x: {
valType: 'data_array',
editType: 'calc+clearAxisTypes',
},
y: {
valType: 'data_array',
editType: 'calc+clearAxisTypes',
},
a: {
valType: 'data_array',
editType: 'calc',
},
a0: {
valType: 'number',
dflt: 0,
editType: 'calc',
},
da: {
valType: 'number',
dflt: 1,
editType: 'calc',
},
b: {
valType: 'data_array',
editType: 'calc',
},
b0: {
valType: 'number',
dflt: 0,
editType: 'calc',
},
db: {
valType: 'number',
dflt: 1,
editType: 'calc',
},
cheaterslope: {
valType: 'number',
dflt: 1,
editType: 'calc',
},
aaxis: axisAttrs,
baxis: axisAttrs,
font: carpetFont,
color: {
valType: 'color',
dflt: colorAttrs.defaultLine,
editType: 'plot',
},
transforms: undefined
};
},{"../../components/color/attributes":365,"../../plots/font_attributes":585,"./axis_attributes":693}],692:[function(_dereq_,module,exports){
'use strict';
var isArrayOrTypedArray = _dereq_('../../lib').isArrayOrTypedArray;
/* This function retrns a set of control points that define a curve aligned along
* either the a or b axis. Exactly one of a or b must be an array defining the range
* spanned.
*
* Honestly this is the most complicated function I've implemente here so far because
* of the way it handles knot insertion and direction/axis-agnostic slices.
*/
module.exports = function(carpet, carpetcd, a, b) {
var idx, tangent, tanIsoIdx, tanIsoPar, segment, refidx;
var p0, p1, v0, v1, start, end, range;
var axis = isArrayOrTypedArray(a) ? 'a' : 'b';
var ax = axis === 'a' ? carpet.aaxis : carpet.baxis;
var smoothing = ax.smoothing;
var toIdx = axis === 'a' ? carpet.a2i : carpet.b2j;
var pt = axis === 'a' ? a : b;
var iso = axis === 'a' ? b : a;
var n = axis === 'a' ? carpetcd.a.length : carpetcd.b.length;
var m = axis === 'a' ? carpetcd.b.length : carpetcd.a.length;
var isoIdx = Math.floor(axis === 'a' ? carpet.b2j(iso) : carpet.a2i(iso));
var xy = axis === 'a' ? function(value) {
return carpet.evalxy([], value, isoIdx);
} : function(value) {
return carpet.evalxy([], isoIdx, value);
};
if(smoothing) {
tanIsoIdx = Math.max(0, Math.min(m - 2, isoIdx));
tanIsoPar = isoIdx - tanIsoIdx;
tangent = axis === 'a' ? function(i, ti) {
return carpet.dxydi([], i, tanIsoIdx, ti, tanIsoPar);
} : function(j, tj) {
return carpet.dxydj([], tanIsoIdx, j, tanIsoPar, tj);
};
}
var vstart = toIdx(pt[0]);
var vend = toIdx(pt[1]);
// So that we can make this work in two directions, flip all of the
// math functions if the direction is from higher to lower indices:
//
// Note that the tolerance is directional!
var dir = vstart < vend ? 1 : -1;
var tol = (vend - vstart) * 1e-8;
var dirfloor = dir > 0 ? Math.floor : Math.ceil;
var dirceil = dir > 0 ? Math.ceil : Math.floor;
var dirmin = dir > 0 ? Math.min : Math.max;
var dirmax = dir > 0 ? Math.max : Math.min;
var idx0 = dirfloor(vstart + tol);
var idx1 = dirceil(vend - tol);
p0 = xy(vstart);
var segments = [[p0]];
for(idx = idx0; idx * dir < idx1 * dir; idx += dir) {
segment = [];
start = dirmax(vstart, idx);
end = dirmin(vend, idx + dir);
range = end - start;
// In order to figure out which cell we're in for the derivative (remember,
// the derivatives are *not* constant across grid lines), let's just average
// the start and end points. This cuts out just a tiny bit of logic and
// there's really no computational difference:
refidx = Math.max(0, Math.min(n - 2, Math.floor(0.5 * (start + end))));
p1 = xy(end);
if(smoothing) {
v0 = tangent(refidx, start - refidx);
v1 = tangent(refidx, end - refidx);
segment.push([
p0[0] + v0[0] / 3 * range,
p0[1] + v0[1] / 3 * range
]);
segment.push([
p1[0] - v1[0] / 3 * range,
p1[1] - v1[1] / 3 * range
]);
}
segment.push(p1);
segments.push(segment);
p0 = p1;
}
return segments;
};
},{"../../lib":503}],693:[function(_dereq_,module,exports){
'use strict';
var fontAttrs = _dereq_('../../plots/font_attributes');
var colorAttrs = _dereq_('../../components/color/attributes');
var axesAttrs = _dereq_('../../plots/cartesian/layout_attributes');
var descriptionWithDates = _dereq_('../../plots/cartesian/axis_format_attributes').descriptionWithDates;
var overrideAll = _dereq_('../../plot_api/edit_types').overrideAll;
module.exports = {
color: {
valType: 'color',
editType: 'calc',
},
smoothing: {
valType: 'number',
dflt: 1,
min: 0,
max: 1.3,
editType: 'calc'
},
title: {
text: {
valType: 'string',
dflt: '',
editType: 'calc',
},
font: fontAttrs({
editType: 'calc',
}),
// TODO how is this different than `title.standoff`
offset: {
valType: 'number',
dflt: 10,
editType: 'calc',
},
editType: 'calc',
},
type: {
valType: 'enumerated',
// '-' means we haven't yet run autotype or couldn't find any data
// it gets turned into linear in gd._fullLayout but not copied back
// to gd.data like the others are.
values: ['-', 'linear', 'date', 'category'],
dflt: '-',
editType: 'calc',
},
autotypenumbers: axesAttrs.autotypenumbers,
autorange: {
valType: 'enumerated',
values: [true, false, 'reversed'],
dflt: true,
editType: 'calc',
},
rangemode: {
valType: 'enumerated',
values: ['normal', 'tozero', 'nonnegative'],
dflt: 'normal',
editType: 'calc',
},
range: {
valType: 'info_array',
editType: 'calc',
items: [
{valType: 'any', editType: 'calc'},
{valType: 'any', editType: 'calc'}
],
},
fixedrange: {
valType: 'boolean',
dflt: false,
editType: 'calc',
},
cheatertype: {
valType: 'enumerated',
values: ['index', 'value'],
dflt: 'value',
editType: 'calc'
},
tickmode: {
valType: 'enumerated',
values: ['linear', 'array'],
dflt: 'array',
editType: 'calc'
},
nticks: {
valType: 'integer',
min: 0,
dflt: 0,
editType: 'calc',
},
tickvals: {
valType: 'data_array',
editType: 'calc',
},
ticktext: {
valType: 'data_array',
editType: 'calc',
},
showticklabels: {
valType: 'enumerated',
values: ['start', 'end', 'both', 'none'],
dflt: 'start',
editType: 'calc',
},
tickfont: fontAttrs({
editType: 'calc',
}),
tickangle: {
valType: 'angle',
dflt: 'auto',
editType: 'calc',
},
tickprefix: {
valType: 'string',
dflt: '',
editType: 'calc',
},
showtickprefix: {
valType: 'enumerated',
values: ['all', 'first', 'last', 'none'],
dflt: 'all',
editType: 'calc',
},
ticksuffix: {
valType: 'string',
dflt: '',
editType: 'calc',
},
showticksuffix: {
valType: 'enumerated',
values: ['all', 'first', 'last', 'none'],
dflt: 'all',
editType: 'calc',
},
showexponent: {
valType: 'enumerated',
values: ['all', 'first', 'last', 'none'],
dflt: 'all',
editType: 'calc',
},
exponentformat: {
valType: 'enumerated',
values: ['none', 'e', 'E', 'power', 'SI', 'B'],
dflt: 'B',
editType: 'calc',
},
minexponent: {
valType: 'number',
dflt: 3,
min: 0,
editType: 'calc',
},
separatethousands: {
valType: 'boolean',
dflt: false,
editType: 'calc',
},
tickformat: {
valType: 'string',
dflt: '',
editType: 'calc',
description: descriptionWithDates('tick label')
},
tickformatstops: overrideAll(axesAttrs.tickformatstops, 'calc', 'from-root'),
categoryorder: {
valType: 'enumerated',
values: [
'trace', 'category ascending', 'category descending', 'array'
/* , 'value ascending', 'value descending'*/ // value ascending / descending to be implemented later
],
dflt: 'trace',
editType: 'calc',
},
categoryarray: {
valType: 'data_array',
editType: 'calc',
},
labelpadding: {
valType: 'integer',
dflt: 10,
editType: 'calc',
},
labelprefix: {
valType: 'string',
editType: 'calc',
},
labelsuffix: {
valType: 'string',
dflt: '',
editType: 'calc',
},
// lines and grids
showline: {
valType: 'boolean',
dflt: false,
editType: 'calc',
},
linecolor: {
valType: 'color',
dflt: colorAttrs.defaultLine,
editType: 'calc',
},
linewidth: {
valType: 'number',
min: 0,
dflt: 1,
editType: 'calc',
},
gridcolor: {
valType: 'color',
editType: 'calc',
},
gridwidth: {
valType: 'number',
min: 0,
dflt: 1,
editType: 'calc',
},
showgrid: {
valType: 'boolean',
dflt: true,
editType: 'calc',
},
minorgridcount: {
valType: 'integer',
min: 0,
dflt: 0,
editType: 'calc',
},
minorgridwidth: {
valType: 'number',
min: 0,
dflt: 1,
editType: 'calc',
},
minorgridcolor: {
valType: 'color',
dflt: colorAttrs.lightLine,
editType: 'calc',
},
startline: {
valType: 'boolean',
editType: 'calc',
},
startlinecolor: {
valType: 'color',
editType: 'calc',
},
startlinewidth: {
valType: 'number',
dflt: 1,
editType: 'calc',
},
endline: {
valType: 'boolean',
editType: 'calc',
},
endlinewidth: {
valType: 'number',
dflt: 1,
editType: 'calc',
},
endlinecolor: {
valType: 'color',
editType: 'calc',
},
tick0: {
valType: 'number',
min: 0,
dflt: 0,
editType: 'calc',
},
dtick: {
valType: 'number',
min: 0,
dflt: 1,
editType: 'calc',
},
arraytick0: {
valType: 'integer',
min: 0,
dflt: 0,
editType: 'calc',
},
arraydtick: {
valType: 'integer',
min: 1,
dflt: 1,
editType: 'calc',
},
_deprecated: {
title: {
valType: 'string',
editType: 'calc',
},
titlefont: fontAttrs({
editType: 'calc',
}),
titleoffset: {
valType: 'number',
dflt: 10,
editType: 'calc',
}
},
editType: 'calc'
};
},{"../../components/color/attributes":365,"../../plot_api/edit_types":536,"../../plots/cartesian/axis_format_attributes":557,"../../plots/cartesian/layout_attributes":569,"../../plots/font_attributes":585}],694:[function(_dereq_,module,exports){
'use strict';
var carpetAttrs = _dereq_('./attributes');
var addOpacity = _dereq_('../../components/color').addOpacity;
var Registry = _dereq_('../../registry');
var Lib = _dereq_('../../lib');
var handleTickValueDefaults = _dereq_('../../plots/cartesian/tick_value_defaults');
var handleTickLabelDefaults = _dereq_('../../plots/cartesian/tick_label_defaults');
var handlePrefixSuffixDefaults = _dereq_('../../plots/cartesian/prefix_suffix_defaults');
var handleCategoryOrderDefaults = _dereq_('../../plots/cartesian/category_order_defaults');
var setConvert = _dereq_('../../plots/cartesian/set_convert');
var autoType = _dereq_('../../plots/cartesian/axis_autotype');
/**
* options: object containing:
*
* letter: 'a' or 'b'
* title: name of the axis (ie 'Colorbar') to go in default title
* name: axis object name (ie 'xaxis') if one should be stored
* font: the default font to inherit
* outerTicks: boolean, should ticks default to outside?
* showGrid: boolean, should gridlines be shown by default?
* data: the plot data to use in choosing auto type
* bgColor: the plot background color, to calculate default gridline colors
*/
module.exports = function handleAxisDefaults(containerIn, containerOut, options) {
var letter = options.letter;
var font = options.font || {};
var attributes = carpetAttrs[letter + 'axis'];
function coerce(attr, dflt) {
return Lib.coerce(containerIn, containerOut, attributes, attr, dflt);
}
function coerce2(attr, dflt) {
return Lib.coerce2(containerIn, containerOut, attributes, attr, dflt);
}
// set up some private properties
if(options.name) {
containerOut._name = options.name;
containerOut._id = options.name;
}
// now figure out type and do some more initialization
coerce('autotypenumbers', options.autotypenumbersDflt);
var axType = coerce('type');
if(axType === '-') {
if(options.data) setAutoType(containerOut, options.data);
if(containerOut.type === '-') {
containerOut.type = 'linear';
} else {
// copy autoType back to input axis
// note that if this object didn't exist
// in the input layout, we have to put it in
// this happens in the main supplyDefaults function
axType = containerIn.type = containerOut.type;
}
}
coerce('smoothing');
coerce('cheatertype');
coerce('showticklabels');
coerce('labelprefix', letter + ' = ');
coerce('labelsuffix');
coerce('showtickprefix');
coerce('showticksuffix');
coerce('separatethousands');
coerce('tickformat');
coerce('exponentformat');
coerce('minexponent');
coerce('showexponent');
coerce('categoryorder');
coerce('tickmode');
coerce('tickvals');
coerce('ticktext');
coerce('tick0');
coerce('dtick');
if(containerOut.tickmode === 'array') {
coerce('arraytick0');
coerce('arraydtick');
}
coerce('labelpadding');
containerOut._hovertitle = letter;
if(axType === 'date') {
var handleCalendarDefaults = Registry.getComponentMethod('calendars', 'handleDefaults');
handleCalendarDefaults(containerIn, containerOut, 'calendar', options.calendar);
}
// we need some of the other functions setConvert attaches, but for
// path finding, override pixel scaling to simple passthrough (identity)
setConvert(containerOut, options.fullLayout);
containerOut.c2p = Lib.identity;
var dfltColor = coerce('color', options.dfltColor);
// if axis.color was provided, use it for fonts too; otherwise,
// inherit from global font color in case that was provided.
var dfltFontColor = (dfltColor === containerIn.color) ? dfltColor : font.color;
var title = coerce('title.text');
if(title) {
Lib.coerceFont(coerce, 'title.font', {
family: font.family,
size: Lib.bigFont(font.size),
color: dfltFontColor
});
coerce('title.offset');
}
coerce('tickangle');
var autoRange = coerce('autorange', !containerOut.isValidRange(containerIn.range));
if(autoRange) coerce('rangemode');
coerce('range');
containerOut.cleanRange();
coerce('fixedrange');
handleTickValueDefaults(containerIn, containerOut, coerce, axType);
handlePrefixSuffixDefaults(containerIn, containerOut, coerce, axType, options);
handleTickLabelDefaults(containerIn, containerOut, coerce, axType, options);
handleCategoryOrderDefaults(containerIn, containerOut, coerce, {
data: options.data,
dataAttr: letter
});
var gridColor = coerce2('gridcolor', addOpacity(dfltColor, 0.3));
var gridWidth = coerce2('gridwidth');
var showGrid = coerce('showgrid');
if(!showGrid) {
delete containerOut.gridcolor;
delete containerOut.gridwidth;
}
var startLineColor = coerce2('startlinecolor', dfltColor);
var startLineWidth = coerce2('startlinewidth', gridWidth);
var showStartLine = coerce('startline', containerOut.showgrid || !!startLineColor || !!startLineWidth);
if(!showStartLine) {
delete containerOut.startlinecolor;
delete containerOut.startlinewidth;
}
var endLineColor = coerce2('endlinecolor', dfltColor);
var endLineWidth = coerce2('endlinewidth', gridWidth);
var showEndLine = coerce('endline', containerOut.showgrid || !!endLineColor || !!endLineWidth);
if(!showEndLine) {
delete containerOut.endlinecolor;
delete containerOut.endlinewidth;
}
if(!showGrid) {
delete containerOut.gridcolor;
delete containerOut.gridWidth;
} else {
coerce('minorgridcount');
coerce('minorgridwidth', gridWidth);
coerce('minorgridcolor', addOpacity(gridColor, 0.06));
if(!containerOut.minorgridcount) {
delete containerOut.minorgridwidth;
delete containerOut.minorgridcolor;
}
}
if(containerOut.showticklabels === 'none') {
delete containerOut.tickfont;
delete containerOut.tickangle;
delete containerOut.showexponent;
delete containerOut.exponentformat;
delete containerOut.minexponent;
delete containerOut.tickformat;
delete containerOut.showticksuffix;
delete containerOut.showtickprefix;
}
if(!containerOut.showticksuffix) {
delete containerOut.ticksuffix;
}
if(!containerOut.showtickprefix) {
delete containerOut.tickprefix;
}
// It needs to be coerced, then something above overrides this deep in the axis code,
// but no, we *actually* want to coerce this.
coerce('tickmode');
return containerOut;
};
function setAutoType(ax, data) {
// new logic: let people specify any type they want,
// only autotype if type is '-'
if(ax.type !== '-') return;
var id = ax._id;
var axLetter = id.charAt(0);
var calAttr = axLetter + 'calendar';
var calendar = ax[calAttr];
ax.type = autoType(data, calendar, {
autotypenumbers: ax.autotypenumbers
});
}
},{"../../components/color":366,"../../lib":503,"../../plots/cartesian/axis_autotype":555,"../../plots/cartesian/category_order_defaults":559,"../../plots/cartesian/prefix_suffix_defaults":573,"../../plots/cartesian/set_convert":576,"../../plots/cartesian/tick_label_defaults":578,"../../plots/cartesian/tick_value_defaults":580,"../../registry":638,"./attributes":691}],695:[function(_dereq_,module,exports){
'use strict';
var Axes = _dereq_('../../plots/cartesian/axes');
var isArray1D = _dereq_('../../lib').isArray1D;
var cheaterBasis = _dereq_('./cheater_basis');
var arrayMinmax = _dereq_('./array_minmax');
var calcGridlines = _dereq_('./calc_gridlines');
var calcLabels = _dereq_('./calc_labels');
var calcClipPath = _dereq_('./calc_clippath');
var clean2dArray = _dereq_('../heatmap/clean_2d_array');
var smoothFill2dArray = _dereq_('./smooth_fill_2d_array');
var convertColumnData = _dereq_('../heatmap/convert_column_xyz');
var setConvert = _dereq_('./set_convert');
module.exports = function calc(gd, trace) {
var xa = Axes.getFromId(gd, trace.xaxis);
var ya = Axes.getFromId(gd, trace.yaxis);
var aax = trace.aaxis;
var bax = trace.baxis;
var x = trace.x;
var y = trace.y;
var cols = [];
if(x && isArray1D(x)) cols.push('x');
if(y && isArray1D(y)) cols.push('y');
if(cols.length) {
convertColumnData(trace, aax, bax, 'a', 'b', cols);
}
var a = trace._a = trace._a || trace.a;
var b = trace._b = trace._b || trace.b;
x = trace._x || trace.x;
y = trace._y || trace.y;
var t = {};
if(trace._cheater) {
var avals = aax.cheatertype === 'index' ? a.length : a;
var bvals = bax.cheatertype === 'index' ? b.length : b;
x = cheaterBasis(avals, bvals, trace.cheaterslope);
}
trace._x = x = clean2dArray(x);
trace._y = y = clean2dArray(y);
// Fill in any undefined values with elliptic smoothing. This doesn't take
// into account the spacing of the values. That is, the derivatives should
// be modified to use a and b values. It's not that hard, but this is already
// moderate overkill for just filling in missing values.
smoothFill2dArray(x, a, b);
smoothFill2dArray(y, a, b);
setConvert(trace);
// create conversion functions that depend on the data
trace.setScale();
// This is a rather expensive scan. Nothing guarantees monotonicity,
// so we need to scan through all data to get proper ranges:
var xrange = arrayMinmax(x);
var yrange = arrayMinmax(y);
var dx = 0.5 * (xrange[1] - xrange[0]);
var xc = 0.5 * (xrange[1] + xrange[0]);
var dy = 0.5 * (yrange[1] - yrange[0]);
var yc = 0.5 * (yrange[1] + yrange[0]);
// Expand the axes to fit the plot, except just grow it by a factor of 1.3
// because the labels should be taken into account except that's difficult
// hence 1.3.
var grow = 1.3;
xrange = [xc - dx * grow, xc + dx * grow];
yrange = [yc - dy * grow, yc + dy * grow];
trace._extremes[xa._id] = Axes.findExtremes(xa, xrange, {padded: true});
trace._extremes[ya._id] = Axes.findExtremes(ya, yrange, {padded: true});
// Enumerate the gridlines, both major and minor, and store them on the trace
// object:
calcGridlines(trace, 'a', 'b');
calcGridlines(trace, 'b', 'a');
// Calculate the text labels for each major gridline and store them on the
// trace object:
calcLabels(trace, aax);
calcLabels(trace, bax);
// Tabulate points for the four segments that bound the axes so that we can
// map to pixel coordinates in the plot function and create a clip rect:
t.clipsegments = calcClipPath(trace._xctrl, trace._yctrl, aax, bax);
t.x = x;
t.y = y;
t.a = a;
t.b = b;
return [t];
};
},{"../../lib":503,"../../plots/cartesian/axes":554,"../heatmap/clean_2d_array":794,"../heatmap/convert_column_xyz":796,"./array_minmax":690,"./calc_clippath":696,"./calc_gridlines":697,"./calc_labels":698,"./cheater_basis":700,"./set_convert":713,"./smooth_fill_2d_array":714}],696:[function(_dereq_,module,exports){
'use strict';
module.exports = function makeClipPath(xctrl, yctrl, aax, bax) {
var i, x, y;
var segments = [];
var asmoothing = !!aax.smoothing;
var bsmoothing = !!bax.smoothing;
var nea1 = xctrl[0].length - 1;
var neb1 = xctrl.length - 1;
// Along the lower a axis:
for(i = 0, x = [], y = []; i <= nea1; i++) {
x[i] = xctrl[0][i];
y[i] = yctrl[0][i];
}
segments.push({x: x, y: y, bicubic: asmoothing});
// Along the upper b axis:
for(i = 0, x = [], y = []; i <= neb1; i++) {
x[i] = xctrl[i][nea1];
y[i] = yctrl[i][nea1];
}
segments.push({x: x, y: y, bicubic: bsmoothing});
// Backwards along the upper a axis:
for(i = nea1, x = [], y = []; i >= 0; i--) {
x[nea1 - i] = xctrl[neb1][i];
y[nea1 - i] = yctrl[neb1][i];
}
segments.push({x: x, y: y, bicubic: asmoothing});
// Backwards along the lower b axis:
for(i = neb1, x = [], y = []; i >= 0; i--) {
x[neb1 - i] = xctrl[i][0];
y[neb1 - i] = yctrl[i][0];
}
segments.push({x: x, y: y, bicubic: bsmoothing});
return segments;
};
},{}],697:[function(_dereq_,module,exports){
'use strict';
var Axes = _dereq_('../../plots/cartesian/axes');
var extendFlat = _dereq_('../../lib/extend').extendFlat;
module.exports = function calcGridlines(trace, axisLetter, crossAxisLetter) {
var i, j, j0;
var eps, bounds, n1, n2, n, value, v;
var j1, v0, v1, d;
var data = trace['_' + axisLetter];
var axis = trace[axisLetter + 'axis'];
var gridlines = axis._gridlines = [];
var minorgridlines = axis._minorgridlines = [];
var boundarylines = axis._boundarylines = [];
var crossData = trace['_' + crossAxisLetter];
var crossAxis = trace[crossAxisLetter + 'axis'];
if(axis.tickmode === 'array') {
axis.tickvals = data.slice();
}
var xcp = trace._xctrl;
var ycp = trace._yctrl;
var nea = xcp[0].length;
var neb = xcp.length;
var na = trace._a.length;
var nb = trace._b.length;
Axes.prepTicks(axis);
// don't leave tickvals in axis looking like an attribute
if(axis.tickmode === 'array') delete axis.tickvals;
// The default is an empty array that will cause the join to remove the gridline if
// it's just disappeared:
// axis._startline = axis._endline = [];
// If the cross axis uses bicubic interpolation, then the grid
// lines fall once every three expanded grid row/cols:
var stride = axis.smoothing ? 3 : 1;
function constructValueGridline(value) {
var i, j, j0, tj, pxy, i0, ti, xy, dxydi0, dxydi1, dxydj0, dxydj1;
var xpoints = [];
var ypoints = [];
var ret = {};
// Search for the fractional grid index giving this line:
if(axisLetter === 'b') {
// For the position we use just the i-j coordinates:
j = trace.b2j(value);
// The derivatives for catmull-rom splines are discontinuous across cell
// boundaries though, so we need to provide both the cell and the position
// within the cell separately:
j0 = Math.floor(Math.max(0, Math.min(nb - 2, j)));
tj = j - j0;
ret.length = nb;
ret.crossLength = na;
ret.xy = function(i) {
return trace.evalxy([], i, j);
};
ret.dxy = function(i0, ti) {
return trace.dxydi([], i0, j0, ti, tj);
};
for(i = 0; i < na; i++) {
i0 = Math.min(na - 2, i);
ti = i - i0;
xy = trace.evalxy([], i, j);
if(crossAxis.smoothing && i > 0) {
// First control point:
dxydi0 = trace.dxydi([], i - 1, j0, 0, tj);
xpoints.push(pxy[0] + dxydi0[0] / 3);
ypoints.push(pxy[1] + dxydi0[1] / 3);
// Second control point:
dxydi1 = trace.dxydi([], i - 1, j0, 1, tj);
xpoints.push(xy[0] - dxydi1[0] / 3);
ypoints.push(xy[1] - dxydi1[1] / 3);
}
xpoints.push(xy[0]);
ypoints.push(xy[1]);
pxy = xy;
}
} else {
i = trace.a2i(value);
i0 = Math.floor(Math.max(0, Math.min(na - 2, i)));
ti = i - i0;
ret.length = na;
ret.crossLength = nb;
ret.xy = function(j) {
return trace.evalxy([], i, j);
};
ret.dxy = function(j0, tj) {
return trace.dxydj([], i0, j0, ti, tj);
};
for(j = 0; j < nb; j++) {
j0 = Math.min(nb - 2, j);
tj = j - j0;
xy = trace.evalxy([], i, j);
if(crossAxis.smoothing && j > 0) {
// First control point:
dxydj0 = trace.dxydj([], i0, j - 1, ti, 0);
xpoints.push(pxy[0] + dxydj0[0] / 3);
ypoints.push(pxy[1] + dxydj0[1] / 3);
// Second control point:
dxydj1 = trace.dxydj([], i0, j - 1, ti, 1);
xpoints.push(xy[0] - dxydj1[0] / 3);
ypoints.push(xy[1] - dxydj1[1] / 3);
}
xpoints.push(xy[0]);
ypoints.push(xy[1]);
pxy = xy;
}
}
ret.axisLetter = axisLetter;
ret.axis = axis;
ret.crossAxis = crossAxis;
ret.value = value;
ret.constvar = crossAxisLetter;
ret.index = n;
ret.x = xpoints;
ret.y = ypoints;
ret.smoothing = crossAxis.smoothing;
return ret;
}
function constructArrayGridline(idx) {
var j, i0, j0, ti, tj;
var xpoints = [];
var ypoints = [];
var ret = {};
ret.length = data.length;
ret.crossLength = crossData.length;
if(axisLetter === 'b') {
j0 = Math.max(0, Math.min(nb - 2, idx));
tj = Math.min(1, Math.max(0, idx - j0));
ret.xy = function(i) {
return trace.evalxy([], i, idx);
};
ret.dxy = function(i0, ti) {
return trace.dxydi([], i0, j0, ti, tj);
};
// In the tickmode: array case, this operation is a simple
// transfer of data:
for(j = 0; j < nea; j++) {
xpoints[j] = xcp[idx * stride][j];
ypoints[j] = ycp[idx * stride][j];
}
} else {
i0 = Math.max(0, Math.min(na - 2, idx));
ti = Math.min(1, Math.max(0, idx - i0));
ret.xy = function(j) {
return trace.evalxy([], idx, j);
};
ret.dxy = function(j0, tj) {
return trace.dxydj([], i0, j0, ti, tj);
};
// In the tickmode: array case, this operation is a simple
// transfer of data:
for(j = 0; j < neb; j++) {
xpoints[j] = xcp[j][idx * stride];
ypoints[j] = ycp[j][idx * stride];
}
}
ret.axisLetter = axisLetter;
ret.axis = axis;
ret.crossAxis = crossAxis;
ret.value = data[idx];
ret.constvar = crossAxisLetter;
ret.index = idx;
ret.x = xpoints;
ret.y = ypoints;
ret.smoothing = crossAxis.smoothing;
return ret;
}
if(axis.tickmode === 'array') {
// var j0 = axis.startline ? 1 : 0;
// var j1 = data.length - (axis.endline ? 1 : 0);
eps = 5e-15;
bounds = [
Math.floor(((data.length - 1) - axis.arraytick0) / axis.arraydtick * (1 + eps)),
Math.ceil((- axis.arraytick0) / axis.arraydtick / (1 + eps))
].sort(function(a, b) {return a - b;});
// Unpack sorted values so we can be sure to avoid infinite loops if something
// is backwards:
n1 = bounds[0] - 1;
n2 = bounds[1] + 1;
// If the axes fall along array lines, then this is a much simpler process since
// we already have all the control points we need
for(n = n1; n < n2; n++) {
j = axis.arraytick0 + axis.arraydtick * n;
if(j < 0 || j > data.length - 1) continue;
gridlines.push(extendFlat(constructArrayGridline(j), {
color: axis.gridcolor,
width: axis.gridwidth
}));
}
for(n = n1; n < n2; n++) {
j0 = axis.arraytick0 + axis.arraydtick * n;
j1 = Math.min(j0 + axis.arraydtick, data.length - 1);
// TODO: fix the bounds computation so we don't have to do a large range and then throw
// out unneeded numbers
if(j0 < 0 || j0 > data.length - 1) continue;
if(j1 < 0 || j1 > data.length - 1) continue;
v0 = data[j0];
v1 = data[j1];
for(i = 0; i < axis.minorgridcount; i++) {
d = j1 - j0;
// TODO: fix the bounds computation so we don't have to do a large range and then throw
// out unneeded numbers
if(d <= 0) continue;
// XXX: This calculation isn't quite right. Off by one somewhere?
v = v0 + (v1 - v0) * (i + 1) / (axis.minorgridcount + 1) * (axis.arraydtick / d);
// TODO: fix the bounds computation so we don't have to do a large range and then throw
// out unneeded numbers
if(v < data[0] || v > data[data.length - 1]) continue;
minorgridlines.push(extendFlat(constructValueGridline(v), {
color: axis.minorgridcolor,
width: axis.minorgridwidth
}));
}
}
if(axis.startline) {
boundarylines.push(extendFlat(constructArrayGridline(0), {
color: axis.startlinecolor,
width: axis.startlinewidth
}));
}
if(axis.endline) {
boundarylines.push(extendFlat(constructArrayGridline(data.length - 1), {
color: axis.endlinecolor,
width: axis.endlinewidth
}));
}
} else {
// If the lines do not fall along the axes, then we have to interpolate
// the contro points and so some math to figure out where the lines are
// in the first place.
// Compute the integer boudns of tick0 + n * dtick that fall within the range
// (roughly speaking):
// Give this a nice generous epsilon. We use at as * (1 + eps) in order to make
// inequalities a little tolerant in a more or less correct manner:
eps = 5e-15;
bounds = [
Math.floor((data[data.length - 1] - axis.tick0) / axis.dtick * (1 + eps)),
Math.ceil((data[0] - axis.tick0) / axis.dtick / (1 + eps))
].sort(function(a, b) {return a - b;});
// Unpack sorted values so we can be sure to avoid infinite loops if something
// is backwards:
n1 = bounds[0];
n2 = bounds[1];
for(n = n1; n <= n2; n++) {
value = axis.tick0 + axis.dtick * n;
gridlines.push(extendFlat(constructValueGridline(value), {
color: axis.gridcolor,
width: axis.gridwidth
}));
}
for(n = n1 - 1; n < n2 + 1; n++) {
value = axis.tick0 + axis.dtick * n;
for(i = 0; i < axis.minorgridcount; i++) {
v = value + axis.dtick * (i + 1) / (axis.minorgridcount + 1);
if(v < data[0] || v > data[data.length - 1]) continue;
minorgridlines.push(extendFlat(constructValueGridline(v), {
color: axis.minorgridcolor,
width: axis.minorgridwidth
}));
}
}
if(axis.startline) {
boundarylines.push(extendFlat(constructValueGridline(data[0]), {
color: axis.startlinecolor,
width: axis.startlinewidth
}));
}
if(axis.endline) {
boundarylines.push(extendFlat(constructValueGridline(data[data.length - 1]), {
color: axis.endlinecolor,
width: axis.endlinewidth
}));
}
}
};
},{"../../lib/extend":493,"../../plots/cartesian/axes":554}],698:[function(_dereq_,module,exports){
'use strict';
var Axes = _dereq_('../../plots/cartesian/axes');
var extendFlat = _dereq_('../../lib/extend').extendFlat;
module.exports = function calcLabels(trace, axis) {
var i, tobj, prefix, suffix, gridline;
var labels = axis._labels = [];
var gridlines = axis._gridlines;
for(i = 0; i < gridlines.length; i++) {
gridline = gridlines[i];
if(['start', 'both'].indexOf(axis.showticklabels) !== -1) {
tobj = Axes.tickText(axis, gridline.value);
extendFlat(tobj, {
prefix: prefix,
suffix: suffix,
endAnchor: true,
xy: gridline.xy(0),
dxy: gridline.dxy(0, 0),
axis: gridline.axis,
length: gridline.crossAxis.length,
font: gridline.axis.tickfont,
isFirst: i === 0,
isLast: i === gridlines.length - 1
});
labels.push(tobj);
}
if(['end', 'both'].indexOf(axis.showticklabels) !== -1) {
tobj = Axes.tickText(axis, gridline.value);
extendFlat(tobj, {
endAnchor: false,
xy: gridline.xy(gridline.crossLength - 1),
dxy: gridline.dxy(gridline.crossLength - 2, 1),
axis: gridline.axis,
length: gridline.crossAxis.length,
font: gridline.axis.tickfont,
isFirst: i === 0,
isLast: i === gridlines.length - 1
});
labels.push(tobj);
}
}
};
},{"../../lib/extend":493,"../../plots/cartesian/axes":554}],699:[function(_dereq_,module,exports){
'use strict';
/*
* Compute the tangent vector according to catmull-rom cubic splines (centripetal,
* I think). That differs from the control point in two ways:
* 1. It is a vector, not a position relative to the point
* 2. the vector is longer than the position relative to p1 by a factor of 3
*
* Close to the boundaries, we'll use these as *quadratic control points, so that
* to make a nice grid, we'll need to divide the tangent by 2 instead of 3. (The
* math works out this way if you work through the bezier derivatives)
*/
var CatmullRomExp = 0.5;
module.exports = function makeControlPoints(p0, p1, p2, smoothness) {
var d1x = p0[0] - p1[0];
var d1y = p0[1] - p1[1];
var d2x = p2[0] - p1[0];
var d2y = p2[1] - p1[1];
var d1a = Math.pow(d1x * d1x + d1y * d1y, CatmullRomExp / 2);
var d2a = Math.pow(d2x * d2x + d2y * d2y, CatmullRomExp / 2);
var numx = (d2a * d2a * d1x - d1a * d1a * d2x) * smoothness;
var numy = (d2a * d2a * d1y - d1a * d1a * d2y) * smoothness;
var denom1 = d2a * (d1a + d2a) * 3;
var denom2 = d1a * (d1a + d2a) * 3;
return [[
p1[0] + (denom1 && numx / denom1),
p1[1] + (denom1 && numy / denom1)
], [
p1[0] - (denom2 && numx / denom2),
p1[1] - (denom2 && numy / denom2)
]];
};
},{}],700:[function(_dereq_,module,exports){
'use strict';
var isArrayOrTypedArray = _dereq_('../../lib').isArrayOrTypedArray;
/*
* Construct a 2D array of cheater values given a, b, and a slope.
* If
*/
module.exports = function(a, b, cheaterslope) {
var i, j, ascal, bscal, aval, bval;
var data = [];
var na = isArrayOrTypedArray(a) ? a.length : a;
var nb = isArrayOrTypedArray(b) ? b.length : b;
var adata = isArrayOrTypedArray(a) ? a : null;
var bdata = isArrayOrTypedArray(b) ? b : null;
// If we're using data, scale it so that for data that's just barely
// not evenly spaced, the switch to value-based indexing is continuous.
// This means evenly spaced data should look the same whether value
// or index cheatertype.
if(adata) {
ascal = (adata.length - 1) / (adata[adata.length - 1] - adata[0]) / (na - 1);
}
if(bdata) {
bscal = (bdata.length - 1) / (bdata[bdata.length - 1] - bdata[0]) / (nb - 1);
}
var xval;
var xmin = Infinity;
var xmax = -Infinity;
for(j = 0; j < nb; j++) {
data[j] = [];
bval = bdata ? (bdata[j] - bdata[0]) * bscal : j / (nb - 1);
for(i = 0; i < na; i++) {
aval = adata ? (adata[i] - adata[0]) * ascal : i / (na - 1);
xval = aval - bval * cheaterslope;
xmin = Math.min(xval, xmin);
xmax = Math.max(xval, xmax);
data[j][i] = xval;
}
}
// Normalize cheater values to the 0-1 range. This comes into play when you have
// multiple cheater plots. After careful consideration, it seems better if cheater
// values are normalized to a consistent range. Otherwise one cheater affects the
// layout of other cheaters on the same axis.
var slope = 1.0 / (xmax - xmin);
var offset = -xmin * slope;
for(j = 0; j < nb; j++) {
for(i = 0; i < na; i++) {
data[j][i] = slope * data[j][i] + offset;
}
}
return data;
};
},{"../../lib":503}],701:[function(_dereq_,module,exports){
'use strict';
var makeControlPoints = _dereq_('./catmull_rom');
var ensureArray = _dereq_('../../lib').ensureArray;
/*
* Turns a coarse grid into a fine grid with control points.
*
* Here's an ASCII representation:
*
* o ----- o ----- o ----- o
* | | | |
* | | | |
* | | | |
* o ----- o ----- o ----- o
* | | | |
* | | | |
* ^ | | | |
* | o ----- o ----- o ----- o
* b | | | | |
* | | | | |
* | | | | |
* o ----- o ----- o ----- o
* ------>
* a
*
* First of all, note that we want to do this in *cartesian* space. This means
* we might run into problems when there are extreme differences in x/y scaling,
* but the alternative is that the topology of the contours might actually be
* view-dependent, which seems worse. As a fallback, the only parameter that
* actually affects the result is the *aspect ratio*, so that we can at least
* improve the situation a bit without going all the way to screen coordinates.
*
* This function flattens the points + tangents into a slightly denser grid of
* *control points*. The resulting grid looks like this:
*
* 9 +--o-o--+ -o-o--+--o-o--+
* 8 o o o o o o o o o o
* | | | |
* 7 o o o o o o o o o o
* 6 +--o-o--+ -o-o--+--o-o--+
* 5 o o o o o o o o o o
* | | | |
* ^ 4 o o o o o o o o o o
* | 3 +--o-o--+ -o-o--+--o-o--+
* b | 2 o o o o o o o o o o
* | | | | |
* | 1 o o o o o o o o o o
* 0 +--o-o--+ -o-o--+--o-o--+
* 0 1 2 3 4 5 6 7 8 9
* ------>
* a
*
* where `o`s represent newly-computed control points. the resulting dimension is
*
* (m - 1) * 3 + 1
* = 3 * m - 2
*
* We could simply store the tangents separately, but that's a nightmare to organize
* in two dimensions since we'll be slicing grid lines in both directions and since
* that basically requires very nearly just as much storage as just storing the dense
* grid.
*
* Wow!
*/
/*
* Catmull-rom is biased at the boundaries toward the interior and we actually
* can't use catmull-rom to compute the control point closest to (but inside)
* the boundary.
*
* A note on plotly's spline interpolation. It uses the catmull rom control point
* closest to the boundary *as* a quadratic control point. This seems incorrect,
* so I've elected not to follow that. Given control points 0 and 1, regular plotly
* splines give *equivalent* cubic control points:
*
* Input:
*
* boundary
* | |
* p0 p2 p3 --> interior
* 0.0 0.667 1.0
* | |
*
* Cubic-equivalent of what plotly splines draw::
*
* boundary
* | |
* p0 p1 p2 p3 --> interior
* 0.0 0.4444 0.8888 1.0
* | |
*
* What this function fills in:
*
* boundary
* | |
* p0 p1 p2 p3 --> interior
* 0.0 0.333 0.667 1.0
* | |
*
* Parameters:
* p0: boundary point
* p2: catmull rom point based on computation at p3
* p3: first grid point
*
* Of course it works whichever way it's oriented; you just need to interpret the
* input/output accordingly.
*/
function inferCubicControlPoint(p0, p2, p3) {
// Extend p1 away from p0 by 50%. This is the equivalent quadratic point that
// would give the same slope as catmull rom at p0.
var p2e0 = -0.5 * p3[0] + 1.5 * p2[0];
var p2e1 = -0.5 * p3[1] + 1.5 * p2[1];
return [
(2 * p2e0 + p0[0]) / 3,
(2 * p2e1 + p0[1]) / 3,
];
}
module.exports = function computeControlPoints(xe, ye, x, y, asmoothing, bsmoothing) {
var i, j, ie, je, xej, yej, xj, yj, cp, p1;
// At this point, we know these dimensions are correct and representative of
// the whole 2D arrays:
var na = x[0].length;
var nb = x.length;
// (n)umber of (e)xpanded points:
var nea = asmoothing ? 3 * na - 2 : na;
var neb = bsmoothing ? 3 * nb - 2 : nb;
xe = ensureArray(xe, neb);
ye = ensureArray(ye, neb);
for(ie = 0; ie < neb; ie++) {
xe[ie] = ensureArray(xe[ie], nea);
ye[ie] = ensureArray(ye[ie], nea);
}
// This loop fills in the X'd points:
//
// . . . .
// . . . .
// | | | |
// | | | |
// X ----- X ----- X ----- X
// | | | |
// | | | |
// | | | |
// X ----- X ----- X ----- X
//
//
// ie = (i) (e)xpanded:
for(j = 0, je = 0; j < nb; j++, je += bsmoothing ? 3 : 1) {
xej = xe[je];
yej = ye[je];
xj = x[j];
yj = y[j];
// je = (j) (e)xpanded:
for(i = 0, ie = 0; i < na; i++, ie += asmoothing ? 3 : 1) {
xej[ie] = xj[i];
yej[ie] = yj[i];
}
}
if(asmoothing) {
// If there's a-smoothing, this loop fills in the X'd points with catmull-rom
// control points computed along the a-axis:
// . . . .
// . . . .
// | | | |
// | | | |
// o -Y-X- o -X-X- o -X-Y- o
// | | | |
// | | | |
// | | | |
// o -Y-X- o -X-X- o -X-Y- o
//
// i: 0 1 2 3
// ie: 0 1 3 3 4 5 6 7 8 9
//
// ------>
// a
//
for(j = 0, je = 0; j < nb; j++, je += bsmoothing ? 3 : 1) {
// Fill in the points marked X for this a-row:
for(i = 1, ie = 3; i < na - 1; i++, ie += 3) {
cp = makeControlPoints(
[x[j][i - 1], y[j][i - 1]],
[x[j][i ], y[j][i]],
[x[j][i + 1], y[j][i + 1]],
asmoothing
);
xe[je][ie - 1] = cp[0][0];
ye[je][ie - 1] = cp[0][1];
xe[je][ie + 1] = cp[1][0];
ye[je][ie + 1] = cp[1][1];
}
// The very first cubic interpolation point (to the left for i = 1 above) is
// used as a *quadratic* interpolation point by the spline drawing function
// which isn't really correct. But for the sake of consistency, we'll use it
// as such. Since we're using cubic splines, that means we need to shorten the
// tangent by 1/3 and also construct a new cubic spline control point 1/3 from
// the original to the i = 0 point.
p1 = inferCubicControlPoint(
[xe[je][0], ye[je][0]],
[xe[je][2], ye[je][2]],
[xe[je][3], ye[je][3]]
);
xe[je][1] = p1[0];
ye[je][1] = p1[1];
// Ditto last points, sans explanation:
p1 = inferCubicControlPoint(
[xe[je][nea - 1], ye[je][nea - 1]],
[xe[je][nea - 3], ye[je][nea - 3]],
[xe[je][nea - 4], ye[je][nea - 4]]
);
xe[je][nea - 2] = p1[0];
ye[je][nea - 2] = p1[1];
}
}
if(bsmoothing) {
// If there's a-smoothing, this loop fills in the X'd points with catmull-rom
// control points computed along the b-axis:
// . . . .
// X X X X X X X X X X
// | | | |
// X X X X X X X X X X
// o -o-o- o -o-o- o -o-o- o
// X X X X X X X X X X
// | | | |
// Y Y Y Y Y Y Y Y Y Y
// o -o-o- o -o-o- o -o-o- o
//
// i: 0 1 2 3
// ie: 0 1 3 3 4 5 6 7 8 9
//
// ------>
// a
//
for(ie = 0; ie < nea; ie++) {
for(je = 3; je < neb - 3; je += 3) {
cp = makeControlPoints(
[xe[je - 3][ie], ye[je - 3][ie]],
[xe[je][ie], ye[je][ie]],
[xe[je + 3][ie], ye[je + 3][ie]],
bsmoothing
);
xe[je - 1][ie] = cp[0][0];
ye[je - 1][ie] = cp[0][1];
xe[je + 1][ie] = cp[1][0];
ye[je + 1][ie] = cp[1][1];
}
// Do the same boundary condition magic for these control points marked Y above:
p1 = inferCubicControlPoint(
[xe[0][ie], ye[0][ie]],
[xe[2][ie], ye[2][ie]],
[xe[3][ie], ye[3][ie]]
);
xe[1][ie] = p1[0];
ye[1][ie] = p1[1];
p1 = inferCubicControlPoint(
[xe[neb - 1][ie], ye[neb - 1][ie]],
[xe[neb - 3][ie], ye[neb - 3][ie]],
[xe[neb - 4][ie], ye[neb - 4][ie]]
);
xe[neb - 2][ie] = p1[0];
ye[neb - 2][ie] = p1[1];
}
}
if(asmoothing && bsmoothing) {
// Do one more pass, this time recomputing exactly what we just computed.
// It's overdetermined since we're peforming catmull-rom in two directions,
// so we'll just average the overdetermined. These points don't lie along the
// grid lines, so note that only grid lines will follow normal plotly spline
// interpolation.
//
// Unless of course there was no b smoothing. Then these intermediate points
// don't actually exist and this section is bypassed.
// . . . .
// o X X o X X o X X o
// | | | |
// o X X o X X o X X o
// o -o-o- o -o-o- o -o-o- o
// o X X o X X o X X o
// | | | |
// o Y Y o Y Y o Y Y o
// o -o-o- o -o-o- o -o-o- o
//
// i: 0 1 2 3
// ie: 0 1 3 3 4 5 6 7 8 9
//
// ------>
// a
//
for(je = 1; je < neb; je += (je + 1) % 3 === 0 ? 2 : 1) {
// Fill in the points marked X for this a-row:
for(ie = 3; ie < nea - 3; ie += 3) {
cp = makeControlPoints(
[xe[je][ie - 3], ye[je][ie - 3]],
[xe[je][ie], ye[je][ie]],
[xe[je][ie + 3], ye[je][ie + 3]],
asmoothing
);
xe[je][ie - 1] = 0.5 * (xe[je][ie - 1] + cp[0][0]);
ye[je][ie - 1] = 0.5 * (ye[je][ie - 1] + cp[0][1]);
xe[je][ie + 1] = 0.5 * (xe[je][ie + 1] + cp[1][0]);
ye[je][ie + 1] = 0.5 * (ye[je][ie + 1] + cp[1][1]);
}
// This case is just slightly different. The computation is the same,
// but having computed this, we'll average with the existing result.
p1 = inferCubicControlPoint(
[xe[je][0], ye[je][0]],
[xe[je][2], ye[je][2]],
[xe[je][3], ye[je][3]]
);
xe[je][1] = 0.5 * (xe[je][1] + p1[0]);
ye[je][1] = 0.5 * (ye[je][1] + p1[1]);
p1 = inferCubicControlPoint(
[xe[je][nea - 1], ye[je][nea - 1]],
[xe[je][nea - 3], ye[je][nea - 3]],
[xe[je][nea - 4], ye[je][nea - 4]]
);
xe[je][nea - 2] = 0.5 * (xe[je][nea - 2] + p1[0]);
ye[je][nea - 2] = 0.5 * (ye[je][nea - 2] + p1[1]);
}
}
return [xe, ye];
};
},{"../../lib":503,"./catmull_rom":699}],702:[function(_dereq_,module,exports){
'use strict';
module.exports = {
RELATIVE_CULL_TOLERANCE: 1e-6
};
},{}],703:[function(_dereq_,module,exports){
'use strict';
/*
* Evaluates the derivative of a list of control point arrays. That is, it expects an array or arrays
* that are expanded relative to the raw data to include the bicubic control points, if applicable. If
* only linear interpolation is desired, then the data points correspond 1-1 along that axis to the
* data itself. Since it's catmull-rom splines in either direction note in particular that the
* derivatives are discontinuous across cell boundaries. That's the reason you need both the *cell*
* and the *point within the cell*.
*
* Also note that the discontinuity of the derivative is in magnitude only. The direction *is*
* continuous across cell boundaries.
*
* For example, to compute the derivative of the xcoordinate halfway between the 7 and 8th i-gridpoints
* and the 10th and 11th j-gridpoints given bicubic smoothing in both dimensions, you'd write:
*
* var deriv = createIDerivativeEvaluator([x], 1, 1);
*
* var dxdi = deriv([], 7, 10, 0.5, 0.5);
* // => [0.12345]
*
* Since there'd be a bunch of duplicate computation to compute multiple derivatives, you can double
* this up by providing more arrays:
*
* var deriv = createIDerivativeEvaluator([x, y], 1, 1);
*
* var dxdi = deriv([], 7, 10, 0.5, 0.5);
* // => [0.12345, 0.78910]
*
* NB: It's presumed that at this point all data has been sanitized and is valid numerical data arrays
* of the correct dimension.
*/
module.exports = function(arrays, asmoothing, bsmoothing) {
if(asmoothing && bsmoothing) {
return function(out, i0, j0, u, v) {
if(!out) out = [];
var f0, f1, f2, f3, ak, k;
// Since it's a grid of control points, the actual indices are * 3:
i0 *= 3;
j0 *= 3;
// Precompute some numbers:
var u2 = u * u;
var ou = 1 - u;
var ou2 = ou * ou;
var ouu2 = ou * u * 2;
var a = -3 * ou2;
var b = 3 * (ou2 - ouu2);
var c = 3 * (ouu2 - u2);
var d = 3 * u2;
var v2 = v * v;
var v3 = v2 * v;
var ov = 1 - v;
var ov2 = ov * ov;
var ov3 = ov2 * ov;
for(k = 0; k < arrays.length; k++) {
ak = arrays[k];
// Compute the derivatives in the u-direction:
f0 = a * ak[j0 ][i0] + b * ak[j0 ][i0 + 1] + c * ak[j0 ][i0 + 2] + d * ak[j0 ][i0 + 3];
f1 = a * ak[j0 + 1][i0] + b * ak[j0 + 1][i0 + 1] + c * ak[j0 + 1][i0 + 2] + d * ak[j0 + 1][i0 + 3];
f2 = a * ak[j0 + 2][i0] + b * ak[j0 + 2][i0 + 1] + c * ak[j0 + 2][i0 + 2] + d * ak[j0 + 2][i0 + 3];
f3 = a * ak[j0 + 3][i0] + b * ak[j0 + 3][i0 + 1] + c * ak[j0 + 3][i0 + 2] + d * ak[j0 + 3][i0 + 3];
// Now just interpolate in the v-direction since it's all separable:
out[k] = ov3 * f0 + 3 * (ov2 * v * f1 + ov * v2 * f2) + v3 * f3;
}
return out;
};
} else if(asmoothing) {
// Handle smooth in the a-direction but linear in the b-direction by performing four
// linear interpolations followed by one cubic interpolation of the result
return function(out, i0, j0, u, v) {
if(!out) out = [];
var f0, f1, k, ak;
i0 *= 3;
var u2 = u * u;
var ou = 1 - u;
var ou2 = ou * ou;
var ouu2 = ou * u * 2;
var a = -3 * ou2;
var b = 3 * (ou2 - ouu2);
var c = 3 * (ouu2 - u2);
var d = 3 * u2;
var ov = 1 - v;
for(k = 0; k < arrays.length; k++) {
ak = arrays[k];
f0 = a * ak[j0 ][i0] + b * ak[j0 ][i0 + 1] + c * ak[j0 ][i0 + 2] + d * ak[j0 ][i0 + 3];
f1 = a * ak[j0 + 1][i0] + b * ak[j0 + 1][i0 + 1] + c * ak[j0 + 1][i0 + 2] + d * ak[j0 + 1][i0 + 3];
out[k] = ov * f0 + v * f1;
}
return out;
};
} else if(bsmoothing) {
// Same as the above case, except reversed. I've disabled the no-unused vars rule
// so that this function is fully interpolation-agnostic. Otherwise it would need
// to be called differently in different cases. Which wouldn't be the worst, but
/* eslint-disable no-unused-vars */
return function(out, i0, j0, u, v) {
/* eslint-enable no-unused-vars */
if(!out) out = [];
var f0, f1, f2, f3, k, ak;
j0 *= 3;
var v2 = v * v;
var v3 = v2 * v;
var ov = 1 - v;
var ov2 = ov * ov;
var ov3 = ov2 * ov;
for(k = 0; k < arrays.length; k++) {
ak = arrays[k];
f0 = ak[j0][i0 + 1] - ak[j0][i0];
f1 = ak[j0 + 1][i0 + 1] - ak[j0 + 1][i0];
f2 = ak[j0 + 2][i0 + 1] - ak[j0 + 2][i0];
f3 = ak[j0 + 3][i0 + 1] - ak[j0 + 3][i0];
out[k] = ov3 * f0 + 3 * (ov2 * v * f1 + ov * v2 * f2) + v3 * f3;
}
return out;
};
} else {
// Finally, both directions are linear:
/* eslint-disable no-unused-vars */
return function(out, i0, j0, u, v) {
/* eslint-enable no-unused-vars */
if(!out) out = [];
var f0, f1, k, ak;
var ov = 1 - v;
for(k = 0; k < arrays.length; k++) {
ak = arrays[k];
f0 = ak[j0][i0 + 1] - ak[j0][i0];
f1 = ak[j0 + 1][i0 + 1] - ak[j0 + 1][i0];
out[k] = ov * f0 + v * f1;
}
return out;
};
}
};
},{}],704:[function(_dereq_,module,exports){
'use strict';
module.exports = function(arrays, asmoothing, bsmoothing) {
if(asmoothing && bsmoothing) {
return function(out, i0, j0, u, v) {
if(!out) out = [];
var f0, f1, f2, f3, ak, k;
// Since it's a grid of control points, the actual indices are * 3:
i0 *= 3;
j0 *= 3;
// Precompute some numbers:
var u2 = u * u;
var u3 = u2 * u;
var ou = 1 - u;
var ou2 = ou * ou;
var ou3 = ou2 * ou;
var v2 = v * v;
var ov = 1 - v;
var ov2 = ov * ov;
var ovv2 = ov * v * 2;
var a = -3 * ov2;
var b = 3 * (ov2 - ovv2);
var c = 3 * (ovv2 - v2);
var d = 3 * v2;
for(k = 0; k < arrays.length; k++) {
ak = arrays[k];
// Compute the derivatives in the v-direction:
f0 = a * ak[j0][i0] + b * ak[j0 + 1][i0] + c * ak[j0 + 2][i0] + d * ak[j0 + 3][i0];
f1 = a * ak[j0][i0 + 1] + b * ak[j0 + 1][i0 + 1] + c * ak[j0 + 2][i0 + 1] + d * ak[j0 + 3][i0 + 1];
f2 = a * ak[j0][i0 + 2] + b * ak[j0 + 1][i0 + 2] + c * ak[j0 + 2][i0 + 2] + d * ak[j0 + 3][i0 + 2];
f3 = a * ak[j0][i0 + 3] + b * ak[j0 + 1][i0 + 3] + c * ak[j0 + 2][i0 + 3] + d * ak[j0 + 3][i0 + 3];
// Now just interpolate in the v-direction since it's all separable:
out[k] = ou3 * f0 + 3 * (ou2 * u * f1 + ou * u2 * f2) + u3 * f3;
}
return out;
};
} else if(asmoothing) {
// Handle smooth in the a-direction but linear in the b-direction by performing four
// linear interpolations followed by one cubic interpolation of the result
return function(out, i0, j0, v, u) {
if(!out) out = [];
var f0, f1, f2, f3, k, ak;
i0 *= 3;
var u2 = u * u;
var u3 = u2 * u;
var ou = 1 - u;
var ou2 = ou * ou;
var ou3 = ou2 * ou;
for(k = 0; k < arrays.length; k++) {
ak = arrays[k];
f0 = ak[j0 + 1][i0] - ak[j0][i0];
f1 = ak[j0 + 1][i0 + 1] - ak[j0][i0 + 1];
f2 = ak[j0 + 1][i0 + 2] - ak[j0][i0 + 2];
f3 = ak[j0 + 1][i0 + 3] - ak[j0][i0 + 3];
out[k] = ou3 * f0 + 3 * (ou2 * u * f1 + ou * u2 * f2) + u3 * f3;
// mathematically equivalent:
// f0 = ou3 * ak[j0 ][i0] + 3 * (ou2 * u * ak[j0 ][i0 + 1] + ou * u2 * ak[j0 ][i0 + 2]) + u3 * ak[j0 ][i0 + 3];
// f1 = ou3 * ak[j0 + 1][i0] + 3 * (ou2 * u * ak[j0 + 1][i0 + 1] + ou * u2 * ak[j0 + 1][i0 + 2]) + u3 * ak[j0 + 1][i0 + 3];
// out[k] = f1 - f0;
}
return out;
};
} else if(bsmoothing) {
// Same as the above case, except reversed:
/* eslint-disable no-unused-vars */
return function(out, i0, j0, u, v) {
/* eslint-enable no-unused-vars */
if(!out) out = [];
var f0, f1, k, ak;
j0 *= 3;
var ou = 1 - u;
var v2 = v * v;
var ov = 1 - v;
var ov2 = ov * ov;
var ovv2 = ov * v * 2;
var a = -3 * ov2;
var b = 3 * (ov2 - ovv2);
var c = 3 * (ovv2 - v2);
var d = 3 * v2;
for(k = 0; k < arrays.length; k++) {
ak = arrays[k];
f0 = a * ak[j0][i0] + b * ak[j0 + 1][i0] + c * ak[j0 + 2][i0] + d * ak[j0 + 3][i0];
f1 = a * ak[j0][i0 + 1] + b * ak[j0 + 1][i0 + 1] + c * ak[j0 + 2][i0 + 1] + d * ak[j0 + 3][i0 + 1];
out[k] = ou * f0 + u * f1;
}
return out;
};
} else {
// Finally, both directions are linear:
/* eslint-disable no-unused-vars */
return function(out, i0, j0, v, u) {
/* eslint-enable no-unused-vars */
if(!out) out = [];
var f0, f1, k, ak;
var ov = 1 - v;
for(k = 0; k < arrays.length; k++) {
ak = arrays[k];
f0 = ak[j0 + 1][i0] - ak[j0][i0];
f1 = ak[j0 + 1][i0 + 1] - ak[j0][i0 + 1];
out[k] = ov * f0 + v * f1;
}
return out;
};
}
};
},{}],705:[function(_dereq_,module,exports){
'use strict';
/*
* Return a function that evaluates a set of linear or bicubic control points.
* This will get evaluated a lot, so we'll at least do a bit of extra work to
* flatten some of the choices. In particular, we'll unroll the linear/bicubic
* combinations and we'll allow computing results in parallel to cut down
* on repeated arithmetic.
*
* Take note that we don't search for the correct range in this function. The
* reason is for consistency due to the corrresponding derivative function. In
* particular, the derivatives aren't continuous across cells, so it's important
* to be able control whether the derivative at a cell boundary is approached
* from one side or the other.
*/
module.exports = function(arrays, na, nb, asmoothing, bsmoothing) {
var imax = na - 2;
var jmax = nb - 2;
if(asmoothing && bsmoothing) {
return function(out, i, j) {
if(!out) out = [];
var f0, f1, f2, f3, ak, k;
var i0 = Math.max(0, Math.min(Math.floor(i), imax));
var j0 = Math.max(0, Math.min(Math.floor(j), jmax));
var u = Math.max(0, Math.min(1, i - i0));
var v = Math.max(0, Math.min(1, j - j0));
// Since it's a grid of control points, the actual indices are * 3:
i0 *= 3;
j0 *= 3;
// Precompute some numbers:
var u2 = u * u;
var u3 = u2 * u;
var ou = 1 - u;
var ou2 = ou * ou;
var ou3 = ou2 * ou;
var v2 = v * v;
var v3 = v2 * v;
var ov = 1 - v;
var ov2 = ov * ov;
var ov3 = ov2 * ov;
for(k = 0; k < arrays.length; k++) {
ak = arrays[k];
f0 = ou3 * ak[j0][i0] + 3 * (ou2 * u * ak[j0][i0 + 1] + ou * u2 * ak[j0][i0 + 2]) + u3 * ak[j0][i0 + 3];
f1 = ou3 * ak[j0 + 1][i0] + 3 * (ou2 * u * ak[j0 + 1][i0 + 1] + ou * u2 * ak[j0 + 1][i0 + 2]) + u3 * ak[j0 + 1][i0 + 3];
f2 = ou3 * ak[j0 + 2][i0] + 3 * (ou2 * u * ak[j0 + 2][i0 + 1] + ou * u2 * ak[j0 + 2][i0 + 2]) + u3 * ak[j0 + 2][i0 + 3];
f3 = ou3 * ak[j0 + 3][i0] + 3 * (ou2 * u * ak[j0 + 3][i0 + 1] + ou * u2 * ak[j0 + 3][i0 + 2]) + u3 * ak[j0 + 3][i0 + 3];
out[k] = ov3 * f0 + 3 * (ov2 * v * f1 + ov * v2 * f2) + v3 * f3;
}
return out;
};
} else if(asmoothing) {
// Handle smooth in the a-direction but linear in the b-direction by performing four
// linear interpolations followed by one cubic interpolation of the result
return function(out, i, j) {
if(!out) out = [];
var i0 = Math.max(0, Math.min(Math.floor(i), imax));
var j0 = Math.max(0, Math.min(Math.floor(j), jmax));
var u = Math.max(0, Math.min(1, i - i0));
var v = Math.max(0, Math.min(1, j - j0));
var f0, f1, f2, f3, k, ak;
i0 *= 3;
var u2 = u * u;
var u3 = u2 * u;
var ou = 1 - u;
var ou2 = ou * ou;
var ou3 = ou2 * ou;
var ov = 1 - v;
for(k = 0; k < arrays.length; k++) {
ak = arrays[k];
f0 = ov * ak[j0][i0] + v * ak[j0 + 1][i0];
f1 = ov * ak[j0][i0 + 1] + v * ak[j0 + 1][i0 + 1];
f2 = ov * ak[j0][i0 + 2] + v * ak[j0 + 1][i0 + 1];
f3 = ov * ak[j0][i0 + 3] + v * ak[j0 + 1][i0 + 1];
out[k] = ou3 * f0 + 3 * (ou2 * u * f1 + ou * u2 * f2) + u3 * f3;
}
return out;
};
} else if(bsmoothing) {
// Same as the above case, except reversed:
return function(out, i, j) {
if(!out) out = [];
var i0 = Math.max(0, Math.min(Math.floor(i), imax));
var j0 = Math.max(0, Math.min(Math.floor(j), jmax));
var u = Math.max(0, Math.min(1, i - i0));
var v = Math.max(0, Math.min(1, j - j0));
var f0, f1, f2, f3, k, ak;
j0 *= 3;
var v2 = v * v;
var v3 = v2 * v;
var ov = 1 - v;
var ov2 = ov * ov;
var ov3 = ov2 * ov;
var ou = 1 - u;
for(k = 0; k < arrays.length; k++) {
ak = arrays[k];
f0 = ou * ak[j0][i0] + u * ak[j0][i0 + 1];
f1 = ou * ak[j0 + 1][i0] + u * ak[j0 + 1][i0 + 1];
f2 = ou * ak[j0 + 2][i0] + u * ak[j0 + 2][i0 + 1];
f3 = ou * ak[j0 + 3][i0] + u * ak[j0 + 3][i0 + 1];
out[k] = ov3 * f0 + 3 * (ov2 * v * f1 + ov * v2 * f2) + v3 * f3;
}
return out;
};
} else {
// Finally, both directions are linear:
return function(out, i, j) {
if(!out) out = [];
var i0 = Math.max(0, Math.min(Math.floor(i), imax));
var j0 = Math.max(0, Math.min(Math.floor(j), jmax));
var u = Math.max(0, Math.min(1, i - i0));
var v = Math.max(0, Math.min(1, j - j0));
var f0, f1, k, ak;
var ov = 1 - v;
var ou = 1 - u;
for(k = 0; k < arrays.length; k++) {
ak = arrays[k];
f0 = ou * ak[j0][i0] + u * ak[j0][i0 + 1];
f1 = ou * ak[j0 + 1][i0] + u * ak[j0 + 1][i0 + 1];
out[k] = ov * f0 + v * f1;
}
return out;
};
}
};
},{}],706:[function(_dereq_,module,exports){
'use strict';
var Lib = _dereq_('../../lib');
var handleXYDefaults = _dereq_('./xy_defaults');
var handleABDefaults = _dereq_('./ab_defaults');
var attributes = _dereq_('./attributes');
var colorAttrs = _dereq_('../../components/color/attributes');
module.exports = function supplyDefaults(traceIn, traceOut, dfltColor, fullLayout) {
function coerce(attr, dflt) {
return Lib.coerce(traceIn, traceOut, attributes, attr, dflt);
}
traceOut._clipPathId = 'clip' + traceOut.uid + 'carpet';
var defaultColor = coerce('color', colorAttrs.defaultLine);
Lib.coerceFont(coerce, 'font');
coerce('carpet');
handleABDefaults(traceIn, traceOut, fullLayout, coerce, defaultColor);
if(!traceOut.a || !traceOut.b) {
traceOut.visible = false;
return;
}
if(traceOut.a.length < 3) {
traceOut.aaxis.smoothing = 0;
}
if(traceOut.b.length < 3) {
traceOut.baxis.smoothing = 0;
}
// NB: the input is x/y arrays. You should know that the *first* dimension of x and y
// corresponds to b and the second to a. This sounds backwards but ends up making sense
// the important part to know is that when you write y[j][i], j goes from 0 to b.length - 1
// and i goes from 0 to a.length - 1.
var validData = handleXYDefaults(traceIn, traceOut, coerce);
if(!validData) {
traceOut.visible = false;
}
if(traceOut._cheater) {
coerce('cheaterslope');
}
};
},{"../../components/color/attributes":365,"../../lib":503,"./ab_defaults":689,"./attributes":691,"./xy_defaults":715}],707:[function(_dereq_,module,exports){
'use strict';
module.exports = {
attributes: _dereq_('./attributes'),
supplyDefaults: _dereq_('./defaults'),
plot: _dereq_('./plot'),
calc: _dereq_('./calc'),
animatable: true,
isContainer: true, // so carpet traces get `calc` before other traces
moduleType: 'trace',
name: 'carpet',
basePlotModule: _dereq_('../../plots/cartesian'),
categories: ['cartesian', 'svg', 'carpet', 'carpetAxis', 'notLegendIsolatable', 'noMultiCategory', 'noHover', 'noSortingByValue'],
meta: {
}
};
},{"../../plots/cartesian":568,"./attributes":691,"./calc":695,"./defaults":706,"./plot":712}],708:[function(_dereq_,module,exports){
'use strict';
/*
* Given a trace, look up the carpet axis by carpet.
*/
module.exports = function(gd, trace) {
var n = gd._fullData.length;
var firstAxis;
for(var i = 0; i < n; i++) {
var maybeCarpet = gd._fullData[i];
if(maybeCarpet.index === trace.index) continue;
if(maybeCarpet.type === 'carpet') {
if(!firstAxis) {
firstAxis = maybeCarpet;
}
if(maybeCarpet.carpet === trace.carpet) {
return maybeCarpet;
}
}
}
return firstAxis;
};
},{}],709:[function(_dereq_,module,exports){
'use strict';
module.exports = function makePath(xp, yp, isBicubic) {
// Prevent d3 errors that would result otherwise:
if(xp.length === 0) return '';
var i;
var path = [];
var stride = isBicubic ? 3 : 1;
for(i = 0; i < xp.length; i += stride) {
path.push(xp[i] + ',' + yp[i]);
if(isBicubic && i < xp.length - stride) {
path.push('C');
path.push([
xp[i + 1] + ',' + yp[i + 1],
xp[i + 2] + ',' + yp[i + 2] + ' ',
].join(' '));
}
}
return path.join(isBicubic ? '' : 'L');
};
},{}],710:[function(_dereq_,module,exports){
'use strict';
var isArrayOrTypedArray = _dereq_('../../lib').isArrayOrTypedArray;
/*
* Map an array of x or y coordinates (c) to screen-space pixel coordinates (p).
* The output array is optional, but if provided, it will be reused without
* reallocation to the extent possible.
*/
module.exports = function mapArray(out, data, func) {
var i;
if(!isArrayOrTypedArray(out)) {
// If not an array, make it an array:
out = [];
} else if(out.length > data.length) {
// If too long, truncate. (If too short, it will grow
// automatically so we don't care about that case)
out = out.slice(0, data.length);
}
for(i = 0; i < data.length; i++) {
out[i] = func(data[i]);
}
return out;
};
},{"../../lib":503}],711:[function(_dereq_,module,exports){
'use strict';
module.exports = function orientText(trace, xaxis, yaxis, xy, dxy, refDxy) {
var dx = dxy[0] * trace.dpdx(xaxis);
var dy = dxy[1] * trace.dpdy(yaxis);
var flip = 1;
var offsetMultiplier = 1.0;
if(refDxy) {
var l1 = Math.sqrt(dxy[0] * dxy[0] + dxy[1] * dxy[1]);
var l2 = Math.sqrt(refDxy[0] * refDxy[0] + refDxy[1] * refDxy[1]);
var dot = (dxy[0] * refDxy[0] + dxy[1] * refDxy[1]) / l1 / l2;
offsetMultiplier = Math.max(0.0, dot);
}
var angle = Math.atan2(dy, dx) * 180 / Math.PI;
if(angle < -90) {
angle += 180;
flip = -flip;
} else if(angle > 90) {
angle -= 180;
flip = -flip;
}
return {
angle: angle,
flip: flip,
p: trace.c2p(xy, xaxis, yaxis),
offsetMultplier: offsetMultiplier
};
};
},{}],712:[function(_dereq_,module,exports){
'use strict';
var d3 = _dereq_('@plotly/d3');
var Drawing = _dereq_('../../components/drawing');
var map1dArray = _dereq_('./map_1d_array');
var makepath = _dereq_('./makepath');
var orientText = _dereq_('./orient_text');
var svgTextUtils = _dereq_('../../lib/svg_text_utils');
var Lib = _dereq_('../../lib');
var strRotate = Lib.strRotate;
var strTranslate = Lib.strTranslate;
var alignmentConstants = _dereq_('../../constants/alignment');
module.exports = function plot(gd, plotinfo, cdcarpet, carpetLayer) {
var xa = plotinfo.xaxis;
var ya = plotinfo.yaxis;
var fullLayout = gd._fullLayout;
var clipLayer = fullLayout._clips;
Lib.makeTraceGroups(carpetLayer, cdcarpet, 'trace').each(function(cd) {
var axisLayer = d3.select(this);
var cd0 = cd[0];
var trace = cd0.trace;
var aax = trace.aaxis;
var bax = trace.baxis;
var minorLayer = Lib.ensureSingle(axisLayer, 'g', 'minorlayer');
var majorLayer = Lib.ensureSingle(axisLayer, 'g', 'majorlayer');
var boundaryLayer = Lib.ensureSingle(axisLayer, 'g', 'boundarylayer');
var labelLayer = Lib.ensureSingle(axisLayer, 'g', 'labellayer');
axisLayer.style('opacity', trace.opacity);
drawGridLines(xa, ya, majorLayer, aax, 'a', aax._gridlines, true);
drawGridLines(xa, ya, majorLayer, bax, 'b', bax._gridlines, true);
drawGridLines(xa, ya, minorLayer, aax, 'a', aax._minorgridlines, true);
drawGridLines(xa, ya, minorLayer, bax, 'b', bax._minorgridlines, true);
// NB: These are not omitted if the lines are not active. The joins must be executed
// in order for them to get cleaned up without a full redraw
drawGridLines(xa, ya, boundaryLayer, aax, 'a-boundary', aax._boundarylines);
drawGridLines(xa, ya, boundaryLayer, bax, 'b-boundary', bax._boundarylines);
var labelOrientationA = drawAxisLabels(gd, xa, ya, trace, cd0, labelLayer, aax._labels, 'a-label');
var labelOrientationB = drawAxisLabels(gd, xa, ya, trace, cd0, labelLayer, bax._labels, 'b-label');
drawAxisTitles(gd, labelLayer, trace, cd0, xa, ya, labelOrientationA, labelOrientationB);
drawClipPath(trace, cd0, clipLayer, xa, ya);
});
};
function drawClipPath(trace, t, layer, xaxis, yaxis) {
var seg, xp, yp, i;
var clip = layer.select('#' + trace._clipPathId);
if(!clip.size()) {
clip = layer.append('clipPath')
.classed('carpetclip', true);
}
var path = Lib.ensureSingle(clip, 'path', 'carpetboundary');
var segments = t.clipsegments;
var segs = [];
for(i = 0; i < segments.length; i++) {
seg = segments[i];
xp = map1dArray([], seg.x, xaxis.c2p);
yp = map1dArray([], seg.y, yaxis.c2p);
segs.push(makepath(xp, yp, seg.bicubic));
}
// This could be optimized ever so slightly to avoid no-op L segments
// at the corners, but it's so negligible that I don't think it's worth
// the extra complexity
var clipPathData = 'M' + segs.join('L') + 'Z';
clip.attr('id', trace._clipPathId);
path.attr('d', clipPathData);
}
function drawGridLines(xaxis, yaxis, layer, axis, axisLetter, gridlines) {
var lineClass = 'const-' + axisLetter + '-lines';
var gridJoin = layer.selectAll('.' + lineClass).data(gridlines);
gridJoin.enter().append('path')
.classed(lineClass, true)
.style('vector-effect', 'non-scaling-stroke');
gridJoin.each(function(d) {
var gridline = d;
var x = gridline.x;
var y = gridline.y;
var xp = map1dArray([], x, xaxis.c2p);
var yp = map1dArray([], y, yaxis.c2p);
var path = 'M' + makepath(xp, yp, gridline.smoothing);
var el = d3.select(this);
el.attr('d', path)
.style('stroke-width', gridline.width)
.style('stroke', gridline.color)
.style('fill', 'none');
});
gridJoin.exit().remove();
}
function drawAxisLabels(gd, xaxis, yaxis, trace, t, layer, labels, labelClass) {
var labelJoin = layer.selectAll('text.' + labelClass).data(labels);
labelJoin.enter().append('text')
.classed(labelClass, true);
var maxExtent = 0;
var labelOrientation = {};
labelJoin.each(function(label, i) {
// Most of the positioning is done in calc_labels. Only the parts that depend upon
// the screen space representation of the x and y axes are here:
var orientation;
if(label.axis.tickangle === 'auto') {
orientation = orientText(trace, xaxis, yaxis, label.xy, label.dxy);
} else {
var angle = (label.axis.tickangle + 180.0) * Math.PI / 180.0;
orientation = orientText(trace, xaxis, yaxis, label.xy, [Math.cos(angle), Math.sin(angle)]);
}
if(!i) {
// TODO: offsetMultiplier? Not currently used anywhere...
labelOrientation = {angle: orientation.angle, flip: orientation.flip};
}
var direction = (label.endAnchor ? -1 : 1) * orientation.flip;
var labelEl = d3.select(this)
.attr({
'text-anchor': direction > 0 ? 'start' : 'end',
'data-notex': 1
})
.call(Drawing.font, label.font)
.text(label.text)
.call(svgTextUtils.convertToTspans, gd);
var bbox = Drawing.bBox(this);
labelEl.attr('transform',
// Translate to the correct point:
strTranslate(orientation.p[0], orientation.p[1]) +
// Rotate to line up with grid line tangent:
strRotate(orientation.angle) +
// Adjust the baseline and indentation:
strTranslate(label.axis.labelpadding * direction, bbox.height * 0.3)
);
maxExtent = Math.max(maxExtent, bbox.width + label.axis.labelpadding);
});
labelJoin.exit().remove();
labelOrientation.maxExtent = maxExtent;
return labelOrientation;
}
function drawAxisTitles(gd, layer, trace, t, xa, ya, labelOrientationA, labelOrientationB) {
var a, b, xy, dxy;
var aMin = Lib.aggNums(Math.min, null, trace.a);
var aMax = Lib.aggNums(Math.max, null, trace.a);
var bMin = Lib.aggNums(Math.min, null, trace.b);
var bMax = Lib.aggNums(Math.max, null, trace.b);
a = 0.5 * (aMin + aMax);
b = bMin;
xy = trace.ab2xy(a, b, true);
dxy = trace.dxyda_rough(a, b);
if(labelOrientationA.angle === undefined) {
Lib.extendFlat(labelOrientationA, orientText(trace, xa, ya, xy, trace.dxydb_rough(a, b)));
}
drawAxisTitle(gd, layer, trace, t, xy, dxy, trace.aaxis, xa, ya, labelOrientationA, 'a-title');
a = aMin;
b = 0.5 * (bMin + bMax);
xy = trace.ab2xy(a, b, true);
dxy = trace.dxydb_rough(a, b);
if(labelOrientationB.angle === undefined) {
Lib.extendFlat(labelOrientationB, orientText(trace, xa, ya, xy, trace.dxyda_rough(a, b)));
}
drawAxisTitle(gd, layer, trace, t, xy, dxy, trace.baxis, xa, ya, labelOrientationB, 'b-title');
}
var lineSpacing = alignmentConstants.LINE_SPACING;
var midShift = ((1 - alignmentConstants.MID_SHIFT) / lineSpacing) + 1;
function drawAxisTitle(gd, layer, trace, t, xy, dxy, axis, xa, ya, labelOrientation, labelClass) {
var data = [];
if(axis.title.text) data.push(axis.title.text);
var titleJoin = layer.selectAll('text.' + labelClass).data(data);
var offset = labelOrientation.maxExtent;
titleJoin.enter().append('text')
.classed(labelClass, true);
// There's only one, but we'll do it as a join so it's updated nicely:
titleJoin.each(function() {
var orientation = orientText(trace, xa, ya, xy, dxy);
if(['start', 'both'].indexOf(axis.showticklabels) === -1) {
offset = 0;
}
// In addition to the size of the labels, add on some extra padding:
var titleSize = axis.title.font.size;
offset += titleSize + axis.title.offset;
var labelNorm = labelOrientation.angle + (labelOrientation.flip < 0 ? 180 : 0);
var angleDiff = (labelNorm - orientation.angle + 450) % 360;
var reverseTitle = angleDiff > 90 && angleDiff < 270;
var el = d3.select(this);
el.text(axis.title.text)
.call(svgTextUtils.convertToTspans, gd);
if(reverseTitle) {
offset = (-svgTextUtils.lineCount(el) + midShift) * lineSpacing * titleSize - offset;
}
el.attr('transform',
strTranslate(orientation.p[0], orientation.p[1]) +
strRotate(orientation.angle) +
strTranslate(0, offset)
)
.attr('text-anchor', 'middle')
.call(Drawing.font, axis.title.font);
});
titleJoin.exit().remove();
}
},{"../../components/drawing":388,"../../constants/alignment":471,"../../lib":503,"../../lib/svg_text_utils":529,"./makepath":709,"./map_1d_array":710,"./orient_text":711,"@plotly/d3":58}],713:[function(_dereq_,module,exports){
'use strict';
var constants = _dereq_('./constants');
var search = _dereq_('../../lib/search').findBin;
var computeControlPoints = _dereq_('./compute_control_points');
var createSplineEvaluator = _dereq_('./create_spline_evaluator');
var createIDerivativeEvaluator = _dereq_('./create_i_derivative_evaluator');
var createJDerivativeEvaluator = _dereq_('./create_j_derivative_evaluator');
/*
* Create conversion functions to go from one basis to another. In particular the letter
* abbreviations are:
*
* i: i/j coordinates along the grid. Integer values correspond to data points
* a: real-valued coordinates along the a/b axes
* c: cartesian x-y coordinates
* p: screen-space pixel coordinates
*/
module.exports = function setConvert(trace) {
var a = trace._a;
var b = trace._b;
var na = a.length;
var nb = b.length;
var aax = trace.aaxis;
var bax = trace.baxis;
// Grab the limits once rather than recomputing the bounds for every point
// independently:
var amin = a[0];
var amax = a[na - 1];
var bmin = b[0];
var bmax = b[nb - 1];
var arange = a[a.length - 1] - a[0];
var brange = b[b.length - 1] - b[0];
// Compute the tolerance so that points are visible slightly outside the
// defined carpet axis:
var atol = arange * constants.RELATIVE_CULL_TOLERANCE;
var btol = brange * constants.RELATIVE_CULL_TOLERANCE;
// Expand the limits to include the relative tolerance:
amin -= atol;
amax += atol;
bmin -= btol;
bmax += btol;
trace.isVisible = function(a, b) {
return a > amin && a < amax && b > bmin && b < bmax;
};
trace.isOccluded = function(a, b) {
return a < amin || a > amax || b < bmin || b > bmax;
};
trace.setScale = function() {
var x = trace._x;
var y = trace._y;
// This is potentially a very expensive step! It does the bulk of the work of constructing
// an expanded basis of control points. Note in particular that it overwrites the existing
// basis without creating a new array since that would potentially thrash the garbage
// collector.
var result = computeControlPoints(trace._xctrl, trace._yctrl, x, y, aax.smoothing, bax.smoothing);
trace._xctrl = result[0];
trace._yctrl = result[1];
// This step is the second step in the process, but it's somewhat simpler. It just unrolls
// some logic since it would be unnecessarily expensive to compute both interpolations
// nearly identically but separately and to include a bunch of linear vs. bicubic logic in
// every single call.
trace.evalxy = createSplineEvaluator([trace._xctrl, trace._yctrl], na, nb, aax.smoothing, bax.smoothing);
trace.dxydi = createIDerivativeEvaluator([trace._xctrl, trace._yctrl], aax.smoothing, bax.smoothing);
trace.dxydj = createJDerivativeEvaluator([trace._xctrl, trace._yctrl], aax.smoothing, bax.smoothing);
};
/*
* Convert from i/j data grid coordinates to a/b values. Note in particular that this
* is *linear* interpolation, even if the data is interpolated bicubically.
*/
trace.i2a = function(i) {
var i0 = Math.max(0, Math.floor(i[0]), na - 2);
var ti = i[0] - i0;
return (1 - ti) * a[i0] + ti * a[i0 + 1];
};
trace.j2b = function(j) {
var j0 = Math.max(0, Math.floor(j[1]), na - 2);
var tj = j[1] - j0;
return (1 - tj) * b[j0] + tj * b[j0 + 1];
};
trace.ij2ab = function(ij) {
return [trace.i2a(ij[0]), trace.j2b(ij[1])];
};
/*
* Convert from a/b coordinates to i/j grid-numbered coordinates. This requires searching
* through the a/b data arrays and assumes they are monotonic, which is presumed to have
* been enforced already.
*/
trace.a2i = function(aval) {
var i0 = Math.max(0, Math.min(search(aval, a), na - 2));
var a0 = a[i0];
var a1 = a[i0 + 1];
return Math.max(0, Math.min(na - 1, i0 + (aval - a0) / (a1 - a0)));
};
trace.b2j = function(bval) {
var j0 = Math.max(0, Math.min(search(bval, b), nb - 2));
var b0 = b[j0];
var b1 = b[j0 + 1];
return Math.max(0, Math.min(nb - 1, j0 + (bval - b0) / (b1 - b0)));
};
trace.ab2ij = function(ab) {
return [trace.a2i(ab[0]), trace.b2j(ab[1])];
};
/*
* Convert from i/j coordinates to x/y caretesian coordinates. This means either bilinear
* or bicubic spline evaluation, but the hard part is already done at this point.
*/
trace.i2c = function(i, j) {
return trace.evalxy([], i, j);
};
trace.ab2xy = function(aval, bval, extrapolate) {
if(!extrapolate && (aval < a[0] || aval > a[na - 1] | bval < b[0] || bval > b[nb - 1])) {
return [false, false];
}
var i = trace.a2i(aval);
var j = trace.b2j(bval);
var pt = trace.evalxy([], i, j);
if(extrapolate) {
// This section uses the boundary derivatives to extrapolate linearly outside
// the defined range. Consider a scatter line with one point inside the carpet
// axis and one point outside. If we don't extrapolate, we can't draw the line
// at all.
var iex = 0;
var jex = 0;
var der = [];
var i0, ti, j0, tj;
if(aval < a[0]) {
i0 = 0;
ti = 0;
iex = (aval - a[0]) / (a[1] - a[0]);
} else if(aval > a[na - 1]) {
i0 = na - 2;
ti = 1;
iex = (aval - a[na - 1]) / (a[na - 1] - a[na - 2]);
} else {
i0 = Math.max(0, Math.min(na - 2, Math.floor(i)));
ti = i - i0;
}
if(bval < b[0]) {
j0 = 0;
tj = 0;
jex = (bval - b[0]) / (b[1] - b[0]);
} else if(bval > b[nb - 1]) {
j0 = nb - 2;
tj = 1;
jex = (bval - b[nb - 1]) / (b[nb - 1] - b[nb - 2]);
} else {
j0 = Math.max(0, Math.min(nb - 2, Math.floor(j)));
tj = j - j0;
}
if(iex) {
trace.dxydi(der, i0, j0, ti, tj);
pt[0] += der[0] * iex;
pt[1] += der[1] * iex;
}
if(jex) {
trace.dxydj(der, i0, j0, ti, tj);
pt[0] += der[0] * jex;
pt[1] += der[1] * jex;
}
}
return pt;
};
trace.c2p = function(xy, xa, ya) {
return [xa.c2p(xy[0]), ya.c2p(xy[1])];
};
trace.p2x = function(p, xa, ya) {
return [xa.p2c(p[0]), ya.p2c(p[1])];
};
trace.dadi = function(i /* , u*/) {
// Right now only a piecewise linear a or b basis is permitted since smoother interpolation
// would cause monotonicity problems. As a retult, u is entirely disregarded in this
// computation, though we'll specify it as a parameter for the sake of completeness and
// future-proofing. It would be possible to use monotonic cubic interpolation, for example.
//
// See: https://en.wikipedia.org/wiki/Monotone_cubic_interpolation
// u = u || 0;
var i0 = Math.max(0, Math.min(a.length - 2, i));
// The step (denominator) is implicitly 1 since that's the grid spacing.
return a[i0 + 1] - a[i0];
};
trace.dbdj = function(j /* , v*/) {
// See above caveats for dadi which also apply here
var j0 = Math.max(0, Math.min(b.length - 2, j));
// The step (denominator) is implicitly 1 since that's the grid spacing.
return b[j0 + 1] - b[j0];
};
// Takes: grid cell coordinate (i, j) and fractional grid cell coordinates (u, v)
// Returns: (dx/da, dy/db)
//
// NB: separate grid cell + fractional grid cell coordinate format is due to the discontinuous
// derivative, as described better in create_i_derivative_evaluator.js
trace.dxyda = function(i0, j0, u, v) {
var dxydi = trace.dxydi(null, i0, j0, u, v);
var dadi = trace.dadi(i0, u);
return [dxydi[0] / dadi, dxydi[1] / dadi];
};
trace.dxydb = function(i0, j0, u, v) {
var dxydj = trace.dxydj(null, i0, j0, u, v);
var dbdj = trace.dbdj(j0, v);
return [dxydj[0] / dbdj, dxydj[1] / dbdj];
};
// Sometimes we don't care about precision and all we really want is decent rough
// directions (as is the case with labels). In that case, we can do a very rough finite
// difference and spare having to worry about precise grid coordinates:
trace.dxyda_rough = function(a, b, reldiff) {
var h = arange * (reldiff || 0.1);
var plus = trace.ab2xy(a + h, b, true);
var minus = trace.ab2xy(a - h, b, true);
return [
(plus[0] - minus[0]) * 0.5 / h,
(plus[1] - minus[1]) * 0.5 / h
];
};
trace.dxydb_rough = function(a, b, reldiff) {
var h = brange * (reldiff || 0.1);
var plus = trace.ab2xy(a, b + h, true);
var minus = trace.ab2xy(a, b - h, true);
return [
(plus[0] - minus[0]) * 0.5 / h,
(plus[1] - minus[1]) * 0.5 / h
];
};
trace.dpdx = function(xa) {
return xa._m;
};
trace.dpdy = function(ya) {
return ya._m;
};
};
},{"../../lib/search":523,"./compute_control_points":701,"./constants":702,"./create_i_derivative_evaluator":703,"./create_j_derivative_evaluator":704,"./create_spline_evaluator":705}],714:[function(_dereq_,module,exports){
'use strict';
var Lib = _dereq_('../../lib');
/*
* Given a 2D array as well as a basis in either direction, this function fills in the
* 2D array using a combination of smoothing and extrapolation. This is rather important
* for carpet plots since it's used for layout so that we can't simply omit or blank out
* points. We need a reasonable guess so that the interpolation puts points somewhere
* even if we were to somehow represent that the data was missing later on.
*
* input:
* - data: 2D array of arrays
* - a: array such that a.length === data[0].length
* - b: array such that b.length === data.length
*/
module.exports = function smoothFill2dArray(data, a, b) {
var i, j, k;
var ip = [];
var jp = [];
// var neighborCnts = [];
var ni = data[0].length;
var nj = data.length;
function avgSurrounding(i, j) {
// As a low-quality start, we can simply average surrounding points (in a not
// non-uniform grid aware manner):
var sum = 0.0;
var val;
var cnt = 0;
if(i > 0 && (val = data[j][i - 1]) !== undefined) {
cnt++;
sum += val;
}
if(i < ni - 1 && (val = data[j][i + 1]) !== undefined) {
cnt++;
sum += val;
}
if(j > 0 && (val = data[j - 1][i]) !== undefined) {
cnt++;
sum += val;
}
if(j < nj - 1 && (val = data[j + 1][i]) !== undefined) {
cnt++;
sum += val;
}
return sum / Math.max(1, cnt);
}
// This loop iterates over all cells. Any cells that are null will be noted and those
// are the only points we will loop over and update via laplace's equation. Points with
// any neighbors will receive the average. If there are no neighboring points, then they
// will be set to zero. Also as we go, track the maximum magnitude so that we can scale
// our tolerance accordingly.
var dmax = 0.0;
for(i = 0; i < ni; i++) {
for(j = 0; j < nj; j++) {
if(data[j][i] === undefined) {
ip.push(i);
jp.push(j);
data[j][i] = avgSurrounding(i, j);
// neighborCnts.push(result.neighbors);
}
dmax = Math.max(dmax, Math.abs(data[j][i]));
}
}
if(!ip.length) return data;
// The tolerance doesn't need to be excessive. It's just for display positioning
var dxp, dxm, dap, dam, dbp, dbm, c, d, diff, reldiff, overrelaxation;
var tol = 1e-5;
var resid = 0;
var itermax = 100;
var iter = 0;
var n = ip.length;
do {
resid = 0;
// Normally we'd loop in two dimensions, but not all points are blank and need
// an update, so we instead loop only over the points that were tabulated above
for(k = 0; k < n; k++) {
i = ip[k];
j = jp[k];
// neighborCnt = neighborCnts[k];
// Track a counter for how many contributions there are. We'll use this counter
// to average at the end, which reduces to laplace's equation with neumann boundary
// conditions on the first derivative (second derivative is zero so that we get
// a nice linear extrapolation at the boundaries).
var boundaryCnt = 0;
var newVal = 0;
var d0, d1, x0, x1, i0, j0;
if(i === 0) {
// If this lies along the i = 0 boundary, extrapolate from the two points
// to the right of this point. Note that the finite differences take into
// account non-uniform grid spacing:
i0 = Math.min(ni - 1, 2);
x0 = a[i0];
x1 = a[1];
d0 = data[j][i0];
d1 = data[j][1];
newVal += d1 + (d1 - d0) * (a[0] - x1) / (x1 - x0);
boundaryCnt++;
} else if(i === ni - 1) {
// If along the high i boundary, extrapolate from the two points to the
// left of this point
i0 = Math.max(0, ni - 3);
x0 = a[i0];
x1 = a[ni - 2];
d0 = data[j][i0];
d1 = data[j][ni - 2];
newVal += d1 + (d1 - d0) * (a[ni - 1] - x1) / (x1 - x0);
boundaryCnt++;
}
if((i === 0 || i === ni - 1) && (j > 0 && j < nj - 1)) {
// If along the min(i) or max(i) boundaries, also smooth vertically as long
// as we're not in a corner. Note that the finite differences used here
// are also aware of nonuniform grid spacing:
dxp = b[j + 1] - b[j];
dxm = b[j] - b[j - 1];
newVal += (dxm * data[j + 1][i] + dxp * data[j - 1][i]) / (dxm + dxp);
boundaryCnt++;
}
if(j === 0) {
// If along the j = 0 boundary, extrpolate this point from the two points
// above it
j0 = Math.min(nj - 1, 2);
x0 = b[j0];
x1 = b[1];
d0 = data[j0][i];
d1 = data[1][i];
newVal += d1 + (d1 - d0) * (b[0] - x1) / (x1 - x0);
boundaryCnt++;
} else if(j === nj - 1) {
// Same for the max j boundary from the cells below it:
j0 = Math.max(0, nj - 3);
x0 = b[j0];
x1 = b[nj - 2];
d0 = data[j0][i];
d1 = data[nj - 2][i];
newVal += d1 + (d1 - d0) * (b[nj - 1] - x1) / (x1 - x0);
boundaryCnt++;
}
if((j === 0 || j === nj - 1) && (i > 0 && i < ni - 1)) {
// Now average points to the left/right as long as not in a corner:
dxp = a[i + 1] - a[i];
dxm = a[i] - a[i - 1];
newVal += (dxm * data[j][i + 1] + dxp * data[j][i - 1]) / (dxm + dxp);
boundaryCnt++;
}
if(!boundaryCnt) {
// If none of the above conditions were triggered, then this is an interior
// point and we can just do a laplace equation update. As above, these differences
// are aware of nonuniform grid spacing:
dap = a[i + 1] - a[i];
dam = a[i] - a[i - 1];
dbp = b[j + 1] - b[j];
dbm = b[j] - b[j - 1];
// These are just some useful constants for the iteration, which is perfectly
// straightforward but a little long to derive from f_xx + f_yy = 0.
c = dap * dam * (dap + dam);
d = dbp * dbm * (dbp + dbm);
newVal = (c * (dbm * data[j + 1][i] + dbp * data[j - 1][i]) +
d * (dam * data[j][i + 1] + dap * data[j][i - 1])) /
(d * (dam + dap) + c * (dbm + dbp));
} else {
// If we did have contributions from the boundary conditions, then average
// the result from the various contributions:
newVal /= boundaryCnt;
}
// Jacobi updates are ridiculously slow to converge, so this approach uses a
// Gauss-seidel iteration which is dramatically faster.
diff = newVal - data[j][i];
reldiff = diff / dmax;
resid += reldiff * reldiff;
// Gauss-Seidel-ish iteration, omega chosen based on heuristics and some
// quick tests.
//
// NB: Don't overrelax the boundarie. Otherwise set an overrelaxation factor
// which is a little low but safely optimal-ish:
overrelaxation = boundaryCnt ? 0 : 0.85;
// If there are four non-null neighbors, then we want a simple average without
// overrelaxation. If all the surrounding points are null, then we want the full
// overrelaxation
//
// Based on experiments, this actually seems to slow down convergence just a bit.
// I'll leave it here for reference in case this needs to be revisited, but
// it seems to work just fine without this.
// if (overrelaxation) overrelaxation *= (4 - neighborCnt) / 4;
data[j][i] += diff * (1 + overrelaxation);
}
resid = Math.sqrt(resid);
} while(iter++ < itermax && resid > tol);
Lib.log('Smoother converged to', resid, 'after', iter, 'iterations');
return data;
};
},{"../../lib":503}],715:[function(_dereq_,module,exports){
'use strict';
var isArray1D = _dereq_('../../lib').isArray1D;
module.exports = function handleXYDefaults(traceIn, traceOut, coerce) {
var x = coerce('x');
var hasX = x && x.length;
var y = coerce('y');
var hasY = y && y.length;
if(!hasX && !hasY) return false;
traceOut._cheater = !x;
if((!hasX || isArray1D(x)) && (!hasY || isArray1D(y))) {
var len = hasX ? x.length : Infinity;
if(hasY) len = Math.min(len, y.length);
if(traceOut.a && traceOut.a.length) len = Math.min(len, traceOut.a.length);
if(traceOut.b && traceOut.b.length) len = Math.min(len, traceOut.b.length);
traceOut._length = len;
} else traceOut._length = null;
return true;
};
},{"../../lib":503}],716:[function(_dereq_,module,exports){
'use strict';
var hovertemplateAttrs = _dereq_('../../plots/template_attributes').hovertemplateAttrs;
var scatterGeoAttrs = _dereq_('../scattergeo/attributes');
var colorScaleAttrs = _dereq_('../../components/colorscale/attributes');
var baseAttrs = _dereq_('../../plots/attributes');
var defaultLine = _dereq_('../../components/color/attributes').defaultLine;
var extendFlat = _dereq_('../../lib/extend').extendFlat;
var scatterGeoMarkerLineAttrs = scatterGeoAttrs.marker.line;
module.exports = extendFlat({
locations: {
valType: 'data_array',
editType: 'calc',
},
locationmode: scatterGeoAttrs.locationmode,
z: {
valType: 'data_array',
editType: 'calc',
},
geojson: extendFlat({}, scatterGeoAttrs.geojson, {
}),
featureidkey: scatterGeoAttrs.featureidkey,
text: extendFlat({}, scatterGeoAttrs.text, {
}),
hovertext: extendFlat({}, scatterGeoAttrs.hovertext, {
}),
marker: {
line: {
color: extendFlat({}, scatterGeoMarkerLineAttrs.color, {dflt: defaultLine}),
width: extendFlat({}, scatterGeoMarkerLineAttrs.width, {dflt: 1}),
editType: 'calc'
},
opacity: {
valType: 'number',
arrayOk: true,
min: 0,
max: 1,
dflt: 1,
editType: 'style',
},
editType: 'calc'
},
selected: {
marker: {
opacity: scatterGeoAttrs.selected.marker.opacity,
editType: 'plot'
},
editType: 'plot'
},
unselected: {
marker: {
opacity: scatterGeoAttrs.unselected.marker.opacity,
editType: 'plot'
},
editType: 'plot'
},
hoverinfo: extendFlat({}, baseAttrs.hoverinfo, {
editType: 'calc',
flags: ['location', 'z', 'text', 'name']
}),
hovertemplate: hovertemplateAttrs(),
showlegend: extendFlat({}, baseAttrs.showlegend, {dflt: false})
},
colorScaleAttrs('', {
cLetter: 'z',
editTypeOverride: 'calc'
})
);
},{"../../components/color/attributes":365,"../../components/colorscale/attributes":373,"../../lib/extend":493,"../../plots/attributes":550,"../../plots/template_attributes":633,"../scattergeo/attributes":967}],717:[function(_dereq_,module,exports){
'use strict';
var isNumeric = _dereq_('fast-isnumeric');
var BADNUM = _dereq_('../../constants/numerical').BADNUM;
var colorscaleCalc = _dereq_('../../components/colorscale/calc');
var arraysToCalcdata = _dereq_('../scatter/arrays_to_calcdata');
var calcSelection = _dereq_('../scatter/calc_selection');
function isNonBlankString(v) {
return v && typeof v === 'string';
}
module.exports = function calc(gd, trace) {
var len = trace._length;
var calcTrace = new Array(len);
var isValidLoc;
if(trace.geojson) {
isValidLoc = function(v) { return isNonBlankString(v) || isNumeric(v); };
} else {
isValidLoc = isNonBlankString;
}
for(var i = 0; i < len; i++) {
var calcPt = calcTrace[i] = {};
var loc = trace.locations[i];
var z = trace.z[i];
if(isValidLoc(loc) && isNumeric(z)) {
calcPt.loc = loc;
calcPt.z = z;
} else {
calcPt.loc = null;
calcPt.z = BADNUM;
}
calcPt.index = i;
}
arraysToCalcdata(calcTrace, trace);
colorscaleCalc(gd, trace, {
vals: trace.z,
containerStr: '',
cLetter: 'z'
});
calcSelection(calcTrace, trace);
return calcTrace;
};
},{"../../components/colorscale/calc":374,"../../constants/numerical":479,"../scatter/arrays_to_calcdata":924,"../scatter/calc_selection":927,"fast-isnumeric":190}],718:[function(_dereq_,module,exports){
'use strict';
var Lib = _dereq_('../../lib');
var colorscaleDefaults = _dereq_('../../components/colorscale/defaults');
var attributes = _dereq_('./attributes');
module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout) {
function coerce(attr, dflt) {
return Lib.coerce(traceIn, traceOut, attributes, attr, dflt);
}
var locations = coerce('locations');
var z = coerce('z');
if(!(locations && locations.length && Lib.isArrayOrTypedArray(z) && z.length)) {
traceOut.visible = false;
return;
}
traceOut._length = Math.min(locations.length, z.length);
var geojson = coerce('geojson');
var locationmodeDflt;
if((typeof geojson === 'string' && geojson !== '') || Lib.isPlainObject(geojson)) {
locationmodeDflt = 'geojson-id';
}
var locationMode = coerce('locationmode', locationmodeDflt);
if(locationMode === 'geojson-id') {
coerce('featureidkey');
}
coerce('text');
coerce('hovertext');
coerce('hovertemplate');
var mlw = coerce('marker.line.width');
if(mlw) coerce('marker.line.color');
coerce('marker.opacity');
colorscaleDefaults(traceIn, traceOut, layout, coerce, {prefix: '', cLetter: 'z'});
Lib.coerceSelectionMarkerOpacity(traceOut, coerce);
};
},{"../../components/colorscale/defaults":376,"../../lib":503,"./attributes":716}],719:[function(_dereq_,module,exports){
'use strict';
module.exports = function eventData(out, pt, trace, cd, pointNumber) {
out.location = pt.location;
out.z = pt.z;
// include feature properties from input geojson
var cdi = cd[pointNumber];
if(cdi.fIn && cdi.fIn.properties) {
out.properties = cdi.fIn.properties;
}
out.ct = cdi.ct;
return out;
};
},{}],720:[function(_dereq_,module,exports){
'use strict';
var Axes = _dereq_('../../plots/cartesian/axes');
var attributes = _dereq_('./attributes');
var fillText = _dereq_('../../lib').fillText;
module.exports = function hoverPoints(pointData, xval, yval) {
var cd = pointData.cd;
var trace = cd[0].trace;
var geo = pointData.subplot;
var pt, i, j, isInside;
var xy = [xval, yval];
var altXy = [xval + 360, yval];
for(i = 0; i < cd.length; i++) {
pt = cd[i];
isInside = false;
if(pt._polygons) {
for(j = 0; j < pt._polygons.length; j++) {
if(pt._polygons[j].contains(xy)) {
isInside = !isInside;
}
// for polygons that cross antimeridian as xval is in [-180, 180]
if(pt._polygons[j].contains(altXy)) {
isInside = !isInside;
}
}
if(isInside) break;
}
}
if(!isInside || !pt) return;
pointData.x0 = pointData.x1 = pointData.xa.c2p(pt.ct);
pointData.y0 = pointData.y1 = pointData.ya.c2p(pt.ct);
pointData.index = pt.index;
pointData.location = pt.loc;
pointData.z = pt.z;
pointData.zLabel = Axes.tickText(geo.mockAxis, geo.mockAxis.c2l(pt.z), 'hover').text;
pointData.hovertemplate = pt.hovertemplate;
makeHoverInfo(pointData, trace, pt);
return [pointData];
};
function makeHoverInfo(pointData, trace, pt) {
if(trace.hovertemplate) return;
var hoverinfo = pt.hi || trace.hoverinfo;
var loc = String(pt.loc);
var parts = (hoverinfo === 'all') ?
attributes.hoverinfo.flags :
hoverinfo.split('+');
var hasName = (parts.indexOf('name') !== -1);
var hasLocation = (parts.indexOf('location') !== -1);
var hasZ = (parts.indexOf('z') !== -1);
var hasText = (parts.indexOf('text') !== -1);
var hasIdAsNameLabel = !hasName && hasLocation;
var text = [];
if(hasIdAsNameLabel) {
pointData.nameOverride = loc;
} else {
if(hasName) pointData.nameOverride = trace.name;
if(hasLocation) text.push(loc);
}
if(hasZ) {
text.push(pointData.zLabel);
}
if(hasText) {
fillText(pt, trace, text);
}
pointData.extraText = text.join('
');
}
},{"../../lib":503,"../../plots/cartesian/axes":554,"./attributes":716}],721:[function(_dereq_,module,exports){
'use strict';
module.exports = {
attributes: _dereq_('./attributes'),
supplyDefaults: _dereq_('./defaults'),
colorbar: _dereq_('../heatmap/colorbar'),
calc: _dereq_('./calc'),
calcGeoJSON: _dereq_('./plot').calcGeoJSON,
plot: _dereq_('./plot').plot,
style: _dereq_('./style').style,
styleOnSelect: _dereq_('./style').styleOnSelect,
hoverPoints: _dereq_('./hover'),
eventData: _dereq_('./event_data'),
selectPoints: _dereq_('./select'),
moduleType: 'trace',
name: 'choropleth',
basePlotModule: _dereq_('../../plots/geo'),
categories: ['geo', 'noOpacity', 'showLegend'],
meta: {
}
};
},{"../../plots/geo":589,"../heatmap/colorbar":795,"./attributes":716,"./calc":717,"./defaults":718,"./event_data":719,"./hover":720,"./plot":722,"./select":723,"./style":724}],722:[function(_dereq_,module,exports){
'use strict';
var d3 = _dereq_('@plotly/d3');
var Lib = _dereq_('../../lib');
var geoUtils = _dereq_('../../lib/geo_location_utils');
var getTopojsonFeatures = _dereq_('../../lib/topojson_utils').getTopojsonFeatures;
var findExtremes = _dereq_('../../plots/cartesian/autorange').findExtremes;
var style = _dereq_('./style').style;
function plot(gd, geo, calcData) {
var choroplethLayer = geo.layers.backplot.select('.choroplethlayer');
Lib.makeTraceGroups(choroplethLayer, calcData, 'trace choropleth').each(function(calcTrace) {
var sel = d3.select(this);
var paths = sel.selectAll('path.choroplethlocation')
.data(Lib.identity);
paths.enter().append('path')
.classed('choroplethlocation', true);
paths.exit().remove();
// call style here within topojson request callback
style(gd, calcTrace);
});
}
function calcGeoJSON(calcTrace, fullLayout) {
var trace = calcTrace[0].trace;
var geoLayout = fullLayout[trace.geo];
var geo = geoLayout._subplot;
var locationmode = trace.locationmode;
var len = trace._length;
var features = locationmode === 'geojson-id' ?
geoUtils.extractTraceFeature(calcTrace) :
getTopojsonFeatures(trace, geo.topojson);
var lonArray = [];
var latArray = [];
for(var i = 0; i < len; i++) {
var calcPt = calcTrace[i];
var feature = locationmode === 'geojson-id' ?
calcPt.fOut :
geoUtils.locationToFeature(locationmode, calcPt.loc, features);
if(feature) {
calcPt.geojson = feature;
calcPt.ct = feature.properties.ct;
calcPt._polygons = geoUtils.feature2polygons(feature);
var bboxFeature = geoUtils.computeBbox(feature);
lonArray.push(bboxFeature[0], bboxFeature[2]);
latArray.push(bboxFeature[1], bboxFeature[3]);
} else {
calcPt.geojson = null;
}
}
if(geoLayout.fitbounds === 'geojson' && locationmode === 'geojson-id') {
var bboxGeojson = geoUtils.computeBbox(geoUtils.getTraceGeojson(trace));
lonArray = [bboxGeojson[0], bboxGeojson[2]];
latArray = [bboxGeojson[1], bboxGeojson[3]];
}
var opts = {padded: true};
trace._extremes.lon = findExtremes(geoLayout.lonaxis._ax, lonArray, opts);
trace._extremes.lat = findExtremes(geoLayout.lataxis._ax, latArray, opts);
}
module.exports = {
calcGeoJSON: calcGeoJSON,
plot: plot
};
},{"../../lib":503,"../../lib/geo_location_utils":496,"../../lib/topojson_utils":532,"../../plots/cartesian/autorange":553,"./style":724,"@plotly/d3":58}],723:[function(_dereq_,module,exports){
'use strict';
module.exports = function selectPoints(searchInfo, selectionTester) {
var cd = searchInfo.cd;
var xa = searchInfo.xaxis;
var ya = searchInfo.yaxis;
var selection = [];
var i, di, ct, x, y;
if(selectionTester === false) {
for(i = 0; i < cd.length; i++) {
cd[i].selected = 0;
}
} else {
for(i = 0; i < cd.length; i++) {
di = cd[i];
ct = di.ct;
if(!ct) continue;
x = xa.c2p(ct);
y = ya.c2p(ct);
if(selectionTester.contains([x, y], null, i, searchInfo)) {
selection.push({
pointNumber: i,
lon: ct[0],
lat: ct[1]
});
di.selected = 1;
} else {
di.selected = 0;
}
}
}
return selection;
};
},{}],724:[function(_dereq_,module,exports){
'use strict';
var d3 = _dereq_('@plotly/d3');
var Color = _dereq_('../../components/color');
var Drawing = _dereq_('../../components/drawing');
var Colorscale = _dereq_('../../components/colorscale');
function style(gd, calcTrace) {
if(calcTrace) styleTrace(gd, calcTrace);
}
function styleTrace(gd, calcTrace) {
var trace = calcTrace[0].trace;
var s = calcTrace[0].node3;
var locs = s.selectAll('.choroplethlocation');
var marker = trace.marker || {};
var markerLine = marker.line || {};
var sclFunc = Colorscale.makeColorScaleFuncFromTrace(trace);
locs.each(function(d) {
d3.select(this)
.attr('fill', sclFunc(d.z))
.call(Color.stroke, d.mlc || markerLine.color)
.call(Drawing.dashLine, '', d.mlw || markerLine.width || 0)
.style('opacity', marker.opacity);
});
Drawing.selectedPointStyle(locs, trace, gd);
}
function styleOnSelect(gd, calcTrace) {
var s = calcTrace[0].node3;
var trace = calcTrace[0].trace;
if(trace.selectedpoints) {
Drawing.selectedPointStyle(s.selectAll('.choroplethlocation'), trace, gd);
} else {
styleTrace(gd, calcTrace);
}
}
module.exports = {
style: style,
styleOnSelect: styleOnSelect
};
},{"../../components/color":366,"../../components/colorscale":378,"../../components/drawing":388,"@plotly/d3":58}],725:[function(_dereq_,module,exports){
'use strict';
var choroplethAttrs = _dereq_('../choropleth/attributes');
var colorScaleAttrs = _dereq_('../../components/colorscale/attributes');
var hovertemplateAttrs = _dereq_('../../plots/template_attributes').hovertemplateAttrs;
var baseAttrs = _dereq_('../../plots/attributes');
var extendFlat = _dereq_('../../lib/extend').extendFlat;
module.exports = extendFlat({
locations: {
valType: 'data_array',
editType: 'calc',
},
// TODO
// Maybe start with only one value (that we could name e.g. 'geojson-id'),
// but eventually:
// - we could also support for our own dist/topojson/*
// .. and locationmode: choroplethAttrs.locationmode,
z: {
valType: 'data_array',
editType: 'calc',
},
// TODO maybe we could also set a "key" to dig out values out of the
// GeoJSON feature `properties` fields?
geojson: {
valType: 'any',
editType: 'calc',
},
featureidkey: extendFlat({}, choroplethAttrs.featureidkey, {
}),
// TODO agree on name / behaviour
//
// 'below' is used currently for layout.mapbox.layers,
// even though it's not very plotly-esque.
//
// Note also, that the mapbox-gl style don't all have the same layers,
// see https://codepen.io/etpinard/pen/ydVMwM for full list
below: {
valType: 'string',
editType: 'plot',
},
text: choroplethAttrs.text,
hovertext: choroplethAttrs.hovertext,
marker: {
line: {
color: extendFlat({}, choroplethAttrs.marker.line.color, {editType: 'plot'}),
width: extendFlat({}, choroplethAttrs.marker.line.width, {editType: 'plot'}),
editType: 'calc'
},
// TODO maybe having a dflt less than 1, together with `below:''` would be better?
opacity: extendFlat({}, choroplethAttrs.marker.opacity, {editType: 'plot'}),
editType: 'calc'
},
selected: {
marker: {
opacity: extendFlat({}, choroplethAttrs.selected.marker.opacity, {editType: 'plot'}),
editType: 'plot'
},
editType: 'plot'
},
unselected: {
marker: {
opacity: extendFlat({}, choroplethAttrs.unselected.marker.opacity, {editType: 'plot'}),
editType: 'plot'
},
editType: 'plot'
},
hoverinfo: choroplethAttrs.hoverinfo,
hovertemplate: hovertemplateAttrs({}, {keys: ['properties']}),
showlegend: extendFlat({}, baseAttrs.showlegend, {dflt: false})
},
colorScaleAttrs('', {
cLetter: 'z',
editTypeOverride: 'calc'
})
);
},{"../../components/colorscale/attributes":373,"../../lib/extend":493,"../../plots/attributes":550,"../../plots/template_attributes":633,"../choropleth/attributes":716}],726:[function(_dereq_,module,exports){
'use strict';
var isNumeric = _dereq_('fast-isnumeric');
var Lib = _dereq_('../../lib');
var Colorscale = _dereq_('../../components/colorscale');
var Drawing = _dereq_('../../components/drawing');
var makeBlank = _dereq_('../../lib/geojson_utils').makeBlank;
var geoUtils = _dereq_('../../lib/geo_location_utils');
/* N.B.
*
* We fetch the GeoJSON files "ourselves" (during
* mapbox.prototype.fetchMapData) where they are stored in a global object
* named `PlotlyGeoAssets` (same as for topojson files in `geo` subplots).
*
* Mapbox does allow using URLs as geojson sources, but does NOT allow filtering
* features by feature `id` that are not numbers (more info in:
* https://github.com/mapbox/mapbox-gl-js/issues/8088).
*/
function convert(calcTrace) {
var trace = calcTrace[0].trace;
var isVisible = trace.visible === true && trace._length !== 0;
var fill = {
layout: {visibility: 'none'},
paint: {}
};
var line = {
layout: {visibility: 'none'},
paint: {}
};
var opts = trace._opts = {
fill: fill,
line: line,
geojson: makeBlank()
};
if(!isVisible) return opts;
var features = geoUtils.extractTraceFeature(calcTrace);
if(!features) return opts;
var sclFunc = Colorscale.makeColorScaleFuncFromTrace(trace);
var marker = trace.marker;
var markerLine = marker.line || {};
var opacityFn;
if(Lib.isArrayOrTypedArray(marker.opacity)) {
opacityFn = function(d) {
var mo = d.mo;
return isNumeric(mo) ? +Lib.constrain(mo, 0, 1) : 0;
};
}
var lineColorFn;
if(Lib.isArrayOrTypedArray(markerLine.color)) {
lineColorFn = function(d) { return d.mlc; };
}
var lineWidthFn;
if(Lib.isArrayOrTypedArray(markerLine.width)) {
lineWidthFn = function(d) { return d.mlw; };
}
for(var i = 0; i < calcTrace.length; i++) {
var cdi = calcTrace[i];
var fOut = cdi.fOut;
if(fOut) {
var props = fOut.properties;
props.fc = sclFunc(cdi.z);
if(opacityFn) props.mo = opacityFn(cdi);
if(lineColorFn) props.mlc = lineColorFn(cdi);
if(lineWidthFn) props.mlw = lineWidthFn(cdi);
cdi.ct = props.ct;
cdi._polygons = geoUtils.feature2polygons(fOut);
}
}
var opacitySetting = opacityFn ?
{type: 'identity', property: 'mo'} :
marker.opacity;
Lib.extendFlat(fill.paint, {
'fill-color': {type: 'identity', property: 'fc'},
'fill-opacity': opacitySetting
});
Lib.extendFlat(line.paint, {
'line-color': lineColorFn ?
{type: 'identity', property: 'mlc'} :
markerLine.color,
'line-width': lineWidthFn ?
{type: 'identity', property: 'mlw'} :
markerLine.width,
'line-opacity': opacitySetting
});
fill.layout.visibility = 'visible';
line.layout.visibility = 'visible';
opts.geojson = {type: 'FeatureCollection', features: features};
convertOnSelect(calcTrace);
return opts;
}
function convertOnSelect(calcTrace) {
var trace = calcTrace[0].trace;
var opts = trace._opts;
var opacitySetting;
if(trace.selectedpoints) {
var fns = Drawing.makeSelectedPointStyleFns(trace);
for(var i = 0; i < calcTrace.length; i++) {
var cdi = calcTrace[i];
if(cdi.fOut) {
cdi.fOut.properties.mo2 = fns.selectedOpacityFn(cdi);
}
}
opacitySetting = {type: 'identity', property: 'mo2'};
} else {
opacitySetting = Lib.isArrayOrTypedArray(trace.marker.opacity) ?
{type: 'identity', property: 'mo'} :
trace.marker.opacity;
}
Lib.extendFlat(opts.fill.paint, {'fill-opacity': opacitySetting});
Lib.extendFlat(opts.line.paint, {'line-opacity': opacitySetting});
return opts;
}
module.exports = {
convert: convert,
convertOnSelect: convertOnSelect
};
},{"../../components/colorscale":378,"../../components/drawing":388,"../../lib":503,"../../lib/geo_location_utils":496,"../../lib/geojson_utils":497,"fast-isnumeric":190}],727:[function(_dereq_,module,exports){
'use strict';
var Lib = _dereq_('../../lib');
var colorscaleDefaults = _dereq_('../../components/colorscale/defaults');
var attributes = _dereq_('./attributes');
module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout) {
function coerce(attr, dflt) {
return Lib.coerce(traceIn, traceOut, attributes, attr, dflt);
}
var locations = coerce('locations');
var z = coerce('z');
var geojson = coerce('geojson');
if(!Lib.isArrayOrTypedArray(locations) || !locations.length ||
!Lib.isArrayOrTypedArray(z) || !z.length ||
!((typeof geojson === 'string' && geojson !== '') || Lib.isPlainObject(geojson))
) {
traceOut.visible = false;
return;
}
coerce('featureidkey');
traceOut._length = Math.min(locations.length, z.length);
coerce('below');
coerce('text');
coerce('hovertext');
coerce('hovertemplate');
var mlw = coerce('marker.line.width');
if(mlw) coerce('marker.line.color');
coerce('marker.opacity');
colorscaleDefaults(traceIn, traceOut, layout, coerce, {prefix: '', cLetter: 'z'});
Lib.coerceSelectionMarkerOpacity(traceOut, coerce);
};
},{"../../components/colorscale/defaults":376,"../../lib":503,"./attributes":725}],728:[function(_dereq_,module,exports){
'use strict';
module.exports = {
attributes: _dereq_('./attributes'),
supplyDefaults: _dereq_('./defaults'),
colorbar: _dereq_('../heatmap/colorbar'),
calc: _dereq_('../choropleth/calc'),
plot: _dereq_('./plot'),
hoverPoints: _dereq_('../choropleth/hover'),
eventData: _dereq_('../choropleth/event_data'),
selectPoints: _dereq_('../choropleth/select'),
styleOnSelect: function(_, cd) {
if(cd) {
var trace = cd[0].trace;
trace._glTrace.updateOnSelect(cd);
}
},
getBelow: function(trace, subplot) {
var mapLayers = subplot.getMapLayers();
// find layer just above top-most "water" layer
// that is not a plotly layer
for(var i = mapLayers.length - 2; i >= 0; i--) {
var layerId = mapLayers[i].id;
if(typeof layerId === 'string' &&
layerId.indexOf('water') === 0
) {
for(var j = i + 1; j < mapLayers.length; j++) {
layerId = mapLayers[j].id;
if(typeof layerId === 'string' &&
layerId.indexOf('plotly-') === -1
) {
return layerId;
}
}
}
}
},
moduleType: 'trace',
name: 'choroplethmapbox',
basePlotModule: _dereq_('../../plots/mapbox'),
categories: ['mapbox', 'gl', 'noOpacity', 'showLegend'],
meta: {
hr_name: 'choropleth_mapbox',
}
};
},{"../../plots/mapbox":613,"../choropleth/calc":717,"../choropleth/event_data":719,"../choropleth/hover":720,"../choropleth/select":723,"../heatmap/colorbar":795,"./attributes":725,"./defaults":727,"./plot":729}],729:[function(_dereq_,module,exports){
'use strict';
var convert = _dereq_('./convert').convert;
var convertOnSelect = _dereq_('./convert').convertOnSelect;
var LAYER_PREFIX = _dereq_('../../plots/mapbox/constants').traceLayerPrefix;
function ChoroplethMapbox(subplot, uid) {
this.type = 'choroplethmapbox';
this.subplot = subplot;
this.uid = uid;
// N.B. fill and line layers share same source
this.sourceId = 'source-' + uid;
this.layerList = [
['fill', LAYER_PREFIX + uid + '-fill'],
['line', LAYER_PREFIX + uid + '-line']
];
// previous 'below' value,
// need this to update it properly
this.below = null;
}
var proto = ChoroplethMapbox.prototype;
proto.update = function(calcTrace) {
this._update(convert(calcTrace));
};
proto.updateOnSelect = function(calcTrace) {
this._update(convertOnSelect(calcTrace));
};
proto._update = function(optsAll) {
var subplot = this.subplot;
var layerList = this.layerList;
var below = subplot.belowLookup['trace-' + this.uid];
subplot.map
.getSource(this.sourceId)
.setData(optsAll.geojson);
if(below !== this.below) {
this._removeLayers();
this._addLayers(optsAll, below);
this.below = below;
}
for(var i = 0; i < layerList.length; i++) {
var item = layerList[i];
var k = item[0];
var id = item[1];
var opts = optsAll[k];
subplot.setOptions(id, 'setLayoutProperty', opts.layout);
if(opts.layout.visibility === 'visible') {
subplot.setOptions(id, 'setPaintProperty', opts.paint);
}
}
};
proto._addLayers = function(optsAll, below) {
var subplot = this.subplot;
var layerList = this.layerList;
var sourceId = this.sourceId;
for(var i = 0; i < layerList.length; i++) {
var item = layerList[i];
var k = item[0];
var opts = optsAll[k];
subplot.addLayer({
type: k,
id: item[1],
source: sourceId,
layout: opts.layout,
paint: opts.paint
}, below);
}
};
proto._removeLayers = function() {
var map = this.subplot.map;
var layerList = this.layerList;
for(var i = layerList.length - 1; i >= 0; i--) {
map.removeLayer(layerList[i][1]);
}
};
proto.dispose = function() {
var map = this.subplot.map;
this._removeLayers();
map.removeSource(this.sourceId);
};
module.exports = function createChoroplethMapbox(subplot, calcTrace) {
var trace = calcTrace[0].trace;
var choroplethMapbox = new ChoroplethMapbox(subplot, trace.uid);
var sourceId = choroplethMapbox.sourceId;
var optsAll = convert(calcTrace);
var below = choroplethMapbox.below = subplot.belowLookup['trace-' + trace.uid];
subplot.map.addSource(sourceId, {
type: 'geojson',
data: optsAll.geojson
});
choroplethMapbox._addLayers(optsAll, below);
// link ref for quick update during selections
calcTrace[0].trace._glTrace = choroplethMapbox;
return choroplethMapbox;
};
},{"../../plots/mapbox/constants":611,"./convert":726}],730:[function(_dereq_,module,exports){
'use strict';
var colorScaleAttrs = _dereq_('../../components/colorscale/attributes');
var axisHoverFormat = _dereq_('../../plots/cartesian/axis_format_attributes').axisHoverFormat;
var hovertemplateAttrs = _dereq_('../../plots/template_attributes').hovertemplateAttrs;
var mesh3dAttrs = _dereq_('../mesh3d/attributes');
var baseAttrs = _dereq_('../../plots/attributes');
var extendFlat = _dereq_('../../lib/extend').extendFlat;
var attrs = {
x: {
valType: 'data_array',
editType: 'calc+clearAxisTypes',
},
y: {
valType: 'data_array',
editType: 'calc+clearAxisTypes',
},
z: {
valType: 'data_array',
editType: 'calc+clearAxisTypes',
},
u: {
valType: 'data_array',
editType: 'calc',
},
v: {
valType: 'data_array',
editType: 'calc',
},
w: {
valType: 'data_array',
editType: 'calc',
},
// TODO add way to specify cone positions independently of the vector field
// provided, similar to MATLAB's coneplot Cx/Cy/Cz meshgrids,
// see https://www.mathworks.com/help/matlab/ref/coneplot.html
//
// Alternatively, if our goal is only to 'fill in gaps' in the vector data,
// we could try to extend the heatmap 'connectgaps' algorithm to 3D.
// From AJ: this particular algorithm which amounts to a Poisson equation,
// both for interpolation and extrapolation - is the right one to use for
// cones too. It makes a field with zero divergence, which is a good
// baseline assumption for vector fields.
//
// cones: {
// // potential attributes to add:
// //
// // - meshmode: 'cartesian-product', 'pts', 'grid'
// //
// // under `meshmode: 'grid'`
// // - (x|y|z)grid.start
// // - (x|y|z)grid.end
// // - (x|y|z)grid.size
//
// x: {
// valType: 'data_array',
// editType: 'calc',
//
// },
// y: {
// valType: 'data_array',
// editType: 'calc',
//
// },
// z: {
// valType: 'data_array',
// editType: 'calc',
//
// },
//
// editType: 'calc',
//
// },
sizemode: {
valType: 'enumerated',
values: ['scaled', 'absolute'],
editType: 'calc',
dflt: 'scaled',
},
sizeref: {
valType: 'number',
editType: 'calc',
min: 0,
},
anchor: {
valType: 'enumerated',
editType: 'calc',
values: ['tip', 'tail', 'cm', 'center'],
dflt: 'cm',
},
text: {
valType: 'string',
dflt: '',
arrayOk: true,
editType: 'calc',
},
hovertext: {
valType: 'string',
dflt: '',
arrayOk: true,
editType: 'calc',
},
hovertemplate: hovertemplateAttrs({editType: 'calc'}, {keys: ['norm']}),
uhoverformat: axisHoverFormat('u', 1),
vhoverformat: axisHoverFormat('v', 1),
whoverformat: axisHoverFormat('w', 1),
xhoverformat: axisHoverFormat('x'),
yhoverformat: axisHoverFormat('y'),
zhoverformat: axisHoverFormat('z'),
showlegend: extendFlat({}, baseAttrs.showlegend, {dflt: false})
};
extendFlat(attrs, colorScaleAttrs('', {
colorAttr: 'u/v/w norm',
showScaleDflt: true,
editTypeOverride: 'calc'
}));
var fromMesh3d = ['opacity', 'lightposition', 'lighting'];
fromMesh3d.forEach(function(k) {
attrs[k] = mesh3dAttrs[k];
});
attrs.hoverinfo = extendFlat({}, baseAttrs.hoverinfo, {
editType: 'calc',
flags: ['x', 'y', 'z', 'u', 'v', 'w', 'norm', 'text', 'name'],
dflt: 'x+y+z+norm+text+name'
});
attrs.transforms = undefined;
module.exports = attrs;
},{"../../components/colorscale/attributes":373,"../../lib/extend":493,"../../plots/attributes":550,"../../plots/cartesian/axis_format_attributes":557,"../../plots/template_attributes":633,"../mesh3d/attributes":866}],731:[function(_dereq_,module,exports){
'use strict';
var colorscaleCalc = _dereq_('../../components/colorscale/calc');
module.exports = function calc(gd, trace) {
var u = trace.u;
var v = trace.v;
var w = trace.w;
var len = Math.min(
trace.x.length, trace.y.length, trace.z.length,
u.length, v.length, w.length
);
var normMax = -Infinity;
var normMin = Infinity;
for(var i = 0; i < len; i++) {
var uu = u[i];
var vv = v[i];
var ww = w[i];
var norm = Math.sqrt(uu * uu + vv * vv + ww * ww);
normMax = Math.max(normMax, norm);
normMin = Math.min(normMin, norm);
}
trace._len = len;
trace._normMax = normMax;
colorscaleCalc(gd, trace, {
vals: [normMin, normMax],
containerStr: '',
cLetter: 'c'
});
};
},{"../../components/colorscale/calc":374}],732:[function(_dereq_,module,exports){
'use strict';
var conePlot = _dereq_('../../../stackgl_modules').gl_cone3d;
var createConeMesh = _dereq_('../../../stackgl_modules').gl_cone3d.createConeMesh;
var simpleMap = _dereq_('../../lib').simpleMap;
var parseColorScale = _dereq_('../../lib/gl_format_color').parseColorScale;
var extractOpts = _dereq_('../../components/colorscale').extractOpts;
var zip3 = _dereq_('../../plots/gl3d/zip3');
function Cone(scene, uid) {
this.scene = scene;
this.uid = uid;
this.mesh = null;
this.data = null;
}
var proto = Cone.prototype;
proto.handlePick = function(selection) {
if(selection.object === this.mesh) {
var selectIndex = selection.index = selection.data.index;
var xx = this.data.x[selectIndex];
var yy = this.data.y[selectIndex];
var zz = this.data.z[selectIndex];
var uu = this.data.u[selectIndex];
var vv = this.data.v[selectIndex];
var ww = this.data.w[selectIndex];
selection.traceCoordinate = [
xx, yy, zz,
uu, vv, ww,
Math.sqrt(uu * uu + vv * vv + ww * ww)
];
var text = this.data.hovertext || this.data.text;
if(Array.isArray(text) && text[selectIndex] !== undefined) {
selection.textLabel = text[selectIndex];
} else if(text) {
selection.textLabel = text;
}
return true;
}
};
var axisName2scaleIndex = {xaxis: 0, yaxis: 1, zaxis: 2};
var anchor2coneOffset = {tip: 1, tail: 0, cm: 0.25, center: 0.5};
var anchor2coneSpan = {tip: 1, tail: 1, cm: 0.75, center: 0.5};
function convert(scene, trace) {
var sceneLayout = scene.fullSceneLayout;
var dataScale = scene.dataScale;
var coneOpts = {};
function toDataCoords(arr, axisName) {
var ax = sceneLayout[axisName];
var scale = dataScale[axisName2scaleIndex[axisName]];
return simpleMap(arr, function(v) { return ax.d2l(v) * scale; });
}
coneOpts.vectors = zip3(
toDataCoords(trace.u, 'xaxis'),
toDataCoords(trace.v, 'yaxis'),
toDataCoords(trace.w, 'zaxis'),
trace._len
);
coneOpts.positions = zip3(
toDataCoords(trace.x, 'xaxis'),
toDataCoords(trace.y, 'yaxis'),
toDataCoords(trace.z, 'zaxis'),
trace._len
);
var cOpts = extractOpts(trace);
coneOpts.colormap = parseColorScale(trace);
coneOpts.vertexIntensityBounds = [cOpts.min / trace._normMax, cOpts.max / trace._normMax];
coneOpts.coneOffset = anchor2coneOffset[trace.anchor];
if(trace.sizemode === 'scaled') {
// unitless sizeref
coneOpts.coneSize = trace.sizeref || 0.5;
} else {
// sizeref here has unit of velocity
coneOpts.coneSize = trace.sizeref && trace._normMax ?
trace.sizeref / trace._normMax :
0.5;
}
var meshData = conePlot(coneOpts);
// pass gl-mesh3d lighting attributes
var lp = trace.lightposition;
meshData.lightPosition = [lp.x, lp.y, lp.z];
meshData.ambient = trace.lighting.ambient;
meshData.diffuse = trace.lighting.diffuse;
meshData.specular = trace.lighting.specular;
meshData.roughness = trace.lighting.roughness;
meshData.fresnel = trace.lighting.fresnel;
meshData.opacity = trace.opacity;
// stash autorange pad value
trace._pad = anchor2coneSpan[trace.anchor] * meshData.vectorScale * meshData.coneScale * trace._normMax;
return meshData;
}
proto.update = function(data) {
this.data = data;
var meshData = convert(this.scene, data);
this.mesh.update(meshData);
};
proto.dispose = function() {
this.scene.glplot.remove(this.mesh);
this.mesh.dispose();
};
function createConeTrace(scene, data) {
var gl = scene.glplot.gl;
var meshData = convert(scene, data);
var mesh = createConeMesh(gl, meshData);
var cone = new Cone(scene, data.uid);
cone.mesh = mesh;
cone.data = data;
mesh._trace = cone;
scene.glplot.add(mesh);
return cone;
}
module.exports = createConeTrace;
},{"../../../stackgl_modules":1119,"../../components/colorscale":378,"../../lib":503,"../../lib/gl_format_color":499,"../../plots/gl3d/zip3":609}],733:[function(_dereq_,module,exports){
'use strict';
var Lib = _dereq_('../../lib');
var colorscaleDefaults = _dereq_('../../components/colorscale/defaults');
var attributes = _dereq_('./attributes');
module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout) {
function coerce(attr, dflt) {
return Lib.coerce(traceIn, traceOut, attributes, attr, dflt);
}
var u = coerce('u');
var v = coerce('v');
var w = coerce('w');
var x = coerce('x');
var y = coerce('y');
var z = coerce('z');
if(
!u || !u.length || !v || !v.length || !w || !w.length ||
!x || !x.length || !y || !y.length || !z || !z.length
) {
traceOut.visible = false;
return;
}
coerce('sizeref');
coerce('sizemode');
coerce('anchor');
coerce('lighting.ambient');
coerce('lighting.diffuse');
coerce('lighting.specular');
coerce('lighting.roughness');
coerce('lighting.fresnel');
coerce('lightposition.x');
coerce('lightposition.y');
coerce('lightposition.z');
colorscaleDefaults(traceIn, traceOut, layout, coerce, {prefix: '', cLetter: 'c'});
coerce('text');
coerce('hovertext');
coerce('hovertemplate');
coerce('uhoverformat');
coerce('vhoverformat');
coerce('whoverformat');
coerce('xhoverformat');
coerce('yhoverformat');
coerce('zhoverformat');
// disable 1D transforms (for now)
traceOut._length = null;
};
},{"../../components/colorscale/defaults":376,"../../lib":503,"./attributes":730}],734:[function(_dereq_,module,exports){
'use strict';
module.exports = {
moduleType: 'trace',
name: 'cone',
basePlotModule: _dereq_('../../plots/gl3d'),
categories: ['gl3d', 'showLegend'],
attributes: _dereq_('./attributes'),
supplyDefaults: _dereq_('./defaults'),
colorbar: {
min: 'cmin',
max: 'cmax'
},
calc: _dereq_('./calc'),
plot: _dereq_('./convert'),
eventData: function(out, pt) {
out.norm = pt.traceCoordinate[6];
return out;
},
meta: {
}
};
},{"../../plots/gl3d":598,"./attributes":730,"./calc":731,"./convert":732,"./defaults":733}],735:[function(_dereq_,module,exports){
'use strict';
var heatmapAttrs = _dereq_('../heatmap/attributes');
var scatterAttrs = _dereq_('../scatter/attributes');
var axisFormat = _dereq_('../../plots/cartesian/axis_format_attributes');
var axisHoverFormat = axisFormat.axisHoverFormat;
var descriptionOnlyNumbers = axisFormat.descriptionOnlyNumbers;
var colorScaleAttrs = _dereq_('../../components/colorscale/attributes');
var dash = _dereq_('../../components/drawing/attributes').dash;
var fontAttrs = _dereq_('../../plots/font_attributes');
var extendFlat = _dereq_('../../lib/extend').extendFlat;
var filterOps = _dereq_('../../constants/filter_ops');
var COMPARISON_OPS2 = filterOps.COMPARISON_OPS2;
var INTERVAL_OPS = filterOps.INTERVAL_OPS;
var scatterLineAttrs = scatterAttrs.line;
module.exports = extendFlat({
z: heatmapAttrs.z,
x: heatmapAttrs.x,
x0: heatmapAttrs.x0,
dx: heatmapAttrs.dx,
y: heatmapAttrs.y,
y0: heatmapAttrs.y0,
dy: heatmapAttrs.dy,
xperiod: heatmapAttrs.xperiod,
yperiod: heatmapAttrs.yperiod,
xperiod0: scatterAttrs.xperiod0,
yperiod0: scatterAttrs.yperiod0,
xperiodalignment: heatmapAttrs.xperiodalignment,
yperiodalignment: heatmapAttrs.yperiodalignment,
text: heatmapAttrs.text,
hovertext: heatmapAttrs.hovertext,
transpose: heatmapAttrs.transpose,
xtype: heatmapAttrs.xtype,
ytype: heatmapAttrs.ytype,
xhoverformat: axisHoverFormat('x'),
yhoverformat: axisHoverFormat('y'),
zhoverformat: axisHoverFormat('z', 1),
hovertemplate: heatmapAttrs.hovertemplate,
hoverongaps: heatmapAttrs.hoverongaps,
connectgaps: extendFlat({}, heatmapAttrs.connectgaps, {
}),
fillcolor: {
valType: 'color',
editType: 'calc',
},
autocontour: {
valType: 'boolean',
dflt: true,
editType: 'calc',
impliedEdits: {
'contours.start': undefined,
'contours.end': undefined,
'contours.size': undefined
},
},
ncontours: {
valType: 'integer',
dflt: 15,
min: 1,
editType: 'calc',
},
contours: {
type: {
valType: 'enumerated',
values: ['levels', 'constraint'],
dflt: 'levels',
editType: 'calc',
},
start: {
valType: 'number',
dflt: null,
editType: 'plot',
impliedEdits: {'^autocontour': false},
},
end: {
valType: 'number',
dflt: null,
editType: 'plot',
impliedEdits: {'^autocontour': false},
},
size: {
valType: 'number',
dflt: null,
min: 0,
editType: 'plot',
impliedEdits: {'^autocontour': false},
},
coloring: {
valType: 'enumerated',
values: ['fill', 'heatmap', 'lines', 'none'],
dflt: 'fill',
editType: 'calc',
},
showlines: {
valType: 'boolean',
dflt: true,
editType: 'plot',
},
showlabels: {
valType: 'boolean',
dflt: false,
editType: 'plot',
},
labelfont: fontAttrs({
editType: 'plot',
colorEditType: 'style',
}),
labelformat: {
valType: 'string',
dflt: '',
editType: 'plot',
description: descriptionOnlyNumbers('contour label')
},
operation: {
valType: 'enumerated',
values: [].concat(COMPARISON_OPS2).concat(INTERVAL_OPS),
dflt: '=',
editType: 'calc',
},
value: {
valType: 'any',
dflt: 0,
editType: 'calc',
},
editType: 'calc',
impliedEdits: {'autocontour': false}
},
line: {
color: extendFlat({}, scatterLineAttrs.color, {
editType: 'style+colorbars',
}),
width: {
valType: 'number',
min: 0,
editType: 'style+colorbars',
},
dash: dash,
smoothing: extendFlat({}, scatterLineAttrs.smoothing, {
}),
editType: 'plot'
}
},
colorScaleAttrs('', {
cLetter: 'z',
autoColorDflt: false,
editTypeOverride: 'calc'
})
);
},{"../../components/colorscale/attributes":373,"../../components/drawing/attributes":387,"../../constants/filter_ops":475,"../../lib/extend":493,"../../plots/cartesian/axis_format_attributes":557,"../../plots/font_attributes":585,"../heatmap/attributes":792,"../scatter/attributes":925}],736:[function(_dereq_,module,exports){
'use strict';
var Colorscale = _dereq_('../../components/colorscale');
var heatmapCalc = _dereq_('../heatmap/calc');
var setContours = _dereq_('./set_contours');
var endPlus = _dereq_('./end_plus');
// most is the same as heatmap calc, then adjust it
// though a few things inside heatmap calc still look for
// contour maps, because the makeBoundArray calls are too entangled
module.exports = function calc(gd, trace) {
var cd = heatmapCalc(gd, trace);
var zOut = cd[0].z;
setContours(trace, zOut);
var contours = trace.contours;
var cOpts = Colorscale.extractOpts(trace);
var cVals;
if(contours.coloring === 'heatmap' && cOpts.auto && trace.autocontour === false) {
var start = contours.start;
var end = endPlus(contours);
var cs = contours.size || 1;
var nc = Math.floor((end - start) / cs) + 1;
if(!isFinite(cs)) {
cs = 1;
nc = 1;
}
var min0 = start - cs / 2;
var max0 = min0 + nc * cs;
cVals = [min0, max0];
} else {
cVals = zOut;
}
Colorscale.calc(gd, trace, {vals: cVals, cLetter: 'z'});
return cd;
};
},{"../../components/colorscale":378,"../heatmap/calc":793,"./end_plus":746,"./set_contours":754}],737:[function(_dereq_,module,exports){
'use strict';
module.exports = function(pathinfo, contours) {
var pi0 = pathinfo[0];
var z = pi0.z;
var i;
switch(contours.type) {
case 'levels':
// Why (just) use z[0][0] and z[0][1]?
//
// N.B. using boundaryMin instead of edgeVal2 here makes the
// `contour_scatter` mock fail
var edgeVal2 = Math.min(z[0][0], z[0][1]);
for(i = 0; i < pathinfo.length; i++) {
var pi = pathinfo[i];
pi.prefixBoundary = !pi.edgepaths.length &&
(edgeVal2 > pi.level || pi.starts.length && edgeVal2 === pi.level);
}
break;
case 'constraint':
// after convertToConstraints, pathinfo has length=0
pi0.prefixBoundary = false;
// joinAllPaths does enough already when edgepaths are present
if(pi0.edgepaths.length) return;
var na = pi0.x.length;
var nb = pi0.y.length;
var boundaryMax = -Infinity;
var boundaryMin = Infinity;
for(i = 0; i < nb; i++) {
boundaryMin = Math.min(boundaryMin, z[i][0]);
boundaryMin = Math.min(boundaryMin, z[i][na - 1]);
boundaryMax = Math.max(boundaryMax, z[i][0]);
boundaryMax = Math.max(boundaryMax, z[i][na - 1]);
}
for(i = 1; i < na - 1; i++) {
boundaryMin = Math.min(boundaryMin, z[0][i]);
boundaryMin = Math.min(boundaryMin, z[nb - 1][i]);
boundaryMax = Math.max(boundaryMax, z[0][i]);
boundaryMax = Math.max(boundaryMax, z[nb - 1][i]);
}
var contoursValue = contours.value;
var v1, v2;
switch(contours._operation) {
case '>':
if(contoursValue > boundaryMax) {
pi0.prefixBoundary = true;
}
break;
case '<':
if(contoursValue < boundaryMin ||
(pi0.starts.length && contoursValue === boundaryMin)) {
pi0.prefixBoundary = true;
}
break;
case '[]':
v1 = Math.min(contoursValue[0], contoursValue[1]);
v2 = Math.max(contoursValue[0], contoursValue[1]);
if(v2 < boundaryMin || v1 > boundaryMax ||
(pi0.starts.length && v2 === boundaryMin)) {
pi0.prefixBoundary = true;
}
break;
case '][':
v1 = Math.min(contoursValue[0], contoursValue[1]);
v2 = Math.max(contoursValue[0], contoursValue[1]);
if(v1 < boundaryMin && v2 > boundaryMax) {
pi0.prefixBoundary = true;
}
break;
}
break;
}
};
},{}],738:[function(_dereq_,module,exports){
'use strict';
var Colorscale = _dereq_('../../components/colorscale');
var makeColorMap = _dereq_('./make_color_map');
var endPlus = _dereq_('./end_plus');
function calc(gd, trace, opts) {
var contours = trace.contours;
var line = trace.line;
var cs = contours.size || 1;
var coloring = contours.coloring;
var colorMap = makeColorMap(trace, {isColorbar: true});
if(coloring === 'heatmap') {
var cOpts = Colorscale.extractOpts(trace);
opts._fillgradient = cOpts.reversescale ?
Colorscale.flipScale(cOpts.colorscale) :
cOpts.colorscale;
opts._zrange = [cOpts.min, cOpts.max];
} else if(coloring === 'fill') {
opts._fillcolor = colorMap;
}
opts._line = {
color: coloring === 'lines' ? colorMap : line.color,
width: contours.showlines !== false ? line.width : 0,
dash: line.dash
};
opts._levels = {
start: contours.start,
end: endPlus(contours),
size: cs
};
}
module.exports = {
min: 'zmin',
max: 'zmax',
calc: calc
};
},{"../../components/colorscale":378,"./end_plus":746,"./make_color_map":751}],739:[function(_dereq_,module,exports){
'use strict';
module.exports = {
// some constants to help with marching squares algorithm
// where does the path start for each index?
BOTTOMSTART: [1, 9, 13, 104, 713],
TOPSTART: [4, 6, 7, 104, 713],
LEFTSTART: [8, 12, 14, 208, 1114],
RIGHTSTART: [2, 3, 11, 208, 1114],
// which way [dx,dy] do we leave a given index?
// saddles are already disambiguated
NEWDELTA: [
null, [-1, 0], [0, -1], [-1, 0],
[1, 0], null, [0, -1], [-1, 0],
[0, 1], [0, 1], null, [0, 1],
[1, 0], [1, 0], [0, -1]
],
// for each saddle, the first index here is used
// for dx||dy<0, the second for dx||dy>0
CHOOSESADDLE: {
104: [4, 1],
208: [2, 8],
713: [7, 13],
1114: [11, 14]
},
// after one index has been used for a saddle, which do we
// substitute to be used up later?
SADDLEREMAINDER: {1: 4, 2: 8, 4: 1, 7: 13, 8: 2, 11: 14, 13: 7, 14: 11},
// length of a contour, as a multiple of the plot area diagonal, per label
LABELDISTANCE: 2,
// number of contour levels after which we start increasing the number of
// labels we draw. Many contours means they will generally be close
// together, so it will be harder to follow a long way to find a label
LABELINCREASE: 10,
// minimum length of a contour line, as a multiple of the label length,
// at which we draw *any* labels
LABELMIN: 3,
// max number of labels to draw on a single contour path, no matter how long
LABELMAX: 10,
// constants for the label position cost function
LABELOPTIMIZER: {
// weight given to edge proximity
EDGECOST: 1,
// weight given to the angle off horizontal
ANGLECOST: 1,
// weight given to distance from already-placed labels
NEIGHBORCOST: 5,
// cost multiplier for labels on the same level
SAMELEVELFACTOR: 10,
// minimum distance (as a multiple of the label length)
// for labels on the same level
SAMELEVELDISTANCE: 5,
// maximum cost before we won't even place the label
MAXCOST: 100,
// number of evenly spaced points to look at in the first
// iteration of the search
INITIALSEARCHPOINTS: 10,
// number of binary search iterations after the initial wide search
ITERATIONS: 5
}
};
},{}],740:[function(_dereq_,module,exports){
'use strict';
var isNumeric = _dereq_('fast-isnumeric');
var handleLabelDefaults = _dereq_('./label_defaults');
var Color = _dereq_('../../components/color');
var addOpacity = Color.addOpacity;
var opacity = Color.opacity;
var filterOps = _dereq_('../../constants/filter_ops');
var CONSTRAINT_REDUCTION = filterOps.CONSTRAINT_REDUCTION;
var COMPARISON_OPS2 = filterOps.COMPARISON_OPS2;
module.exports = function handleConstraintDefaults(traceIn, traceOut, coerce, layout, defaultColor, opts) {
var contours = traceOut.contours;
var showLines, lineColor, fillColor;
var operation = coerce('contours.operation');
contours._operation = CONSTRAINT_REDUCTION[operation];
handleConstraintValueDefaults(coerce, contours);
if(operation === '=') {
showLines = contours.showlines = true;
} else {
showLines = coerce('contours.showlines');
fillColor = coerce('fillcolor', addOpacity(
(traceIn.line || {}).color || defaultColor, 0.5
));
}
if(showLines) {
var lineDfltColor = fillColor && opacity(fillColor) ?
addOpacity(traceOut.fillcolor, 1) :
defaultColor;
lineColor = coerce('line.color', lineDfltColor);
coerce('line.width', 2);
coerce('line.dash');
}
coerce('line.smoothing');
handleLabelDefaults(coerce, layout, lineColor, opts);
};
function handleConstraintValueDefaults(coerce, contours) {
var zvalue;
if(COMPARISON_OPS2.indexOf(contours.operation) === -1) {
// Requires an array of two numbers:
coerce('contours.value', [0, 1]);
if(!Array.isArray(contours.value)) {
if(isNumeric(contours.value)) {
zvalue = parseFloat(contours.value);
contours.value = [zvalue, zvalue + 1];
}
} else if(contours.value.length > 2) {
contours.value = contours.value.slice(2);
} else if(contours.length === 0) {
contours.value = [0, 1];
} else if(contours.length < 2) {
zvalue = parseFloat(contours.value[0]);
contours.value = [zvalue, zvalue + 1];
} else {
contours.value = [
parseFloat(contours.value[0]),
parseFloat(contours.value[1])
];
}
} else {
// Requires a single scalar:
coerce('contours.value', 0);
if(!isNumeric(contours.value)) {
if(Array.isArray(contours.value)) {
contours.value = parseFloat(contours.value[0]);
} else {
contours.value = 0;
}
}
}
}
},{"../../components/color":366,"../../constants/filter_ops":475,"./label_defaults":750,"fast-isnumeric":190}],741:[function(_dereq_,module,exports){
'use strict';
var filterOps = _dereq_('../../constants/filter_ops');
var isNumeric = _dereq_('fast-isnumeric');
// This syntax conforms to the existing filter transform syntax, but we don't care
// about open vs. closed intervals for simply drawing contours constraints:
module.exports = {
'[]': makeRangeSettings('[]'),
'][': makeRangeSettings(']['),
'>': makeInequalitySettings('>'),
'<': makeInequalitySettings('<'),
'=': makeInequalitySettings('=')
};
// This does not in any way shape or form support calendars. It's adapted from
// transforms/filter.js.
function coerceValue(operation, value) {
var hasArrayValue = Array.isArray(value);
var coercedValue;
function coerce(value) {
return isNumeric(value) ? (+value) : null;
}
if(filterOps.COMPARISON_OPS2.indexOf(operation) !== -1) {
coercedValue = hasArrayValue ? coerce(value[0]) : coerce(value);
} else if(filterOps.INTERVAL_OPS.indexOf(operation) !== -1) {
coercedValue = hasArrayValue ?
[coerce(value[0]), coerce(value[1])] :
[coerce(value), coerce(value)];
} else if(filterOps.SET_OPS.indexOf(operation) !== -1) {
coercedValue = hasArrayValue ? value.map(coerce) : [coerce(value)];
}
return coercedValue;
}
// Returns a parabola scaled so that the min/max is either +/- 1 and zero at the two values
// provided. The data is mapped by this function when constructing intervals so that it's
// very easy to construct contours as normal.
function makeRangeSettings(operation) {
return function(value) {
value = coerceValue(operation, value);
// Ensure proper ordering:
var min = Math.min(value[0], value[1]);
var max = Math.max(value[0], value[1]);
return {
start: min,
end: max,
size: max - min
};
};
}
function makeInequalitySettings(operation) {
return function(value) {
value = coerceValue(operation, value);
return {
start: value,
end: Infinity,
size: Infinity
};
};
}
},{"../../constants/filter_ops":475,"fast-isnumeric":190}],742:[function(_dereq_,module,exports){
'use strict';
module.exports = function handleContourDefaults(traceIn, traceOut, coerce, coerce2) {
var contourStart = coerce2('contours.start');
var contourEnd = coerce2('contours.end');
var missingEnd = (contourStart === false) || (contourEnd === false);
// normally we only need size if autocontour is off. But contour.calc
// pushes its calculated contour size back to the input trace, so for
// things like restyle that can call supplyDefaults without calc
// after the initial draw, we can just reuse the previous calculation
var contourSize = coerce('contours.size');
var autoContour;
if(missingEnd) autoContour = traceOut.autocontour = true;
else autoContour = coerce('autocontour', false);
if(autoContour || !contourSize) coerce('ncontours');
};
},{}],743:[function(_dereq_,module,exports){
'use strict';
var Lib = _dereq_('../../lib');
// The contour extraction is great, except it totally fails for constraints because we
// need weird range loops and flipped contours instead of the usual format. This function
// does some weird manipulation of the extracted pathinfo data such that it magically
// draws contours correctly *as* constraints.
//
// ** I do not know which "weird range loops" the comment above is referring to.
module.exports = function(pathinfo, operation) {
var i, pi0, pi1;
var op0 = function(arr) { return arr.reverse(); };
var op1 = function(arr) { return arr; };
switch(operation) {
case '=':
case '<':
return pathinfo;
case '>':
if(pathinfo.length !== 1) {
Lib.warn('Contour data invalid for the specified inequality operation.');
}
// In this case there should be exactly one contour levels in pathinfo.
// We flip all of the data. This will draw the contour as closed.
pi0 = pathinfo[0];
for(i = 0; i < pi0.edgepaths.length; i++) {
pi0.edgepaths[i] = op0(pi0.edgepaths[i]);
}
for(i = 0; i < pi0.paths.length; i++) {
pi0.paths[i] = op0(pi0.paths[i]);
}
for(i = 0; i < pi0.starts.length; i++) {
pi0.starts[i] = op0(pi0.starts[i]);
}
return pathinfo;
case '][':
var tmp = op0;
op0 = op1;
op1 = tmp;
// It's a nice rule, except this definitely *is* what's intended here.
/* eslint-disable: no-fallthrough */
case '[]':
/* eslint-enable: no-fallthrough */
if(pathinfo.length !== 2) {
Lib.warn('Contour data invalid for the specified inequality range operation.');
}
// In this case there should be exactly two contour levels in pathinfo.
// - We concatenate the info into one pathinfo.
// - We must also flip all of the data in the `[]` case.
// This will draw the contours as closed.
pi0 = copyPathinfo(pathinfo[0]);
pi1 = copyPathinfo(pathinfo[1]);
for(i = 0; i < pi0.edgepaths.length; i++) {
pi0.edgepaths[i] = op0(pi0.edgepaths[i]);
}
for(i = 0; i < pi0.paths.length; i++) {
pi0.paths[i] = op0(pi0.paths[i]);
}
for(i = 0; i < pi0.starts.length; i++) {
pi0.starts[i] = op0(pi0.starts[i]);
}
while(pi1.edgepaths.length) {
pi0.edgepaths.push(op1(pi1.edgepaths.shift()));
}
while(pi1.paths.length) {
pi0.paths.push(op1(pi1.paths.shift()));
}
while(pi1.starts.length) {
pi0.starts.push(op1(pi1.starts.shift()));
}
return [pi0];
}
};
function copyPathinfo(pi) {
return Lib.extendFlat({}, pi, {
edgepaths: Lib.extendDeep([], pi.edgepaths),
paths: Lib.extendDeep([], pi.paths),
starts: Lib.extendDeep([], pi.starts)
});
}
},{"../../lib":503}],744:[function(_dereq_,module,exports){
'use strict';
var Lib = _dereq_('../../lib');
var handleXYZDefaults = _dereq_('../heatmap/xyz_defaults');
var handlePeriodDefaults = _dereq_('../scatter/period_defaults');
var handleConstraintDefaults = _dereq_('./constraint_defaults');
var handleContoursDefaults = _dereq_('./contours_defaults');
var handleStyleDefaults = _dereq_('./style_defaults');
var attributes = _dereq_('./attributes');
module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout) {
function coerce(attr, dflt) {
return Lib.coerce(traceIn, traceOut, attributes, attr, dflt);
}
function coerce2(attr) {
return Lib.coerce2(traceIn, traceOut, attributes, attr);
}
var len = handleXYZDefaults(traceIn, traceOut, coerce, layout);
if(!len) {
traceOut.visible = false;
return;
}
handlePeriodDefaults(traceIn, traceOut, layout, coerce);
coerce('xhoverformat');
coerce('yhoverformat');
coerce('text');
coerce('hovertext');
coerce('hovertemplate');
coerce('hoverongaps');
var isConstraint = (coerce('contours.type') === 'constraint');
coerce('connectgaps', Lib.isArray1D(traceOut.z));
if(isConstraint) {
handleConstraintDefaults(traceIn, traceOut, coerce, layout, defaultColor);
} else {
handleContoursDefaults(traceIn, traceOut, coerce, coerce2);
handleStyleDefaults(traceIn, traceOut, coerce, layout);
}
};
},{"../../lib":503,"../heatmap/xyz_defaults":806,"../scatter/period_defaults":945,"./attributes":735,"./constraint_defaults":740,"./contours_defaults":742,"./style_defaults":756}],745:[function(_dereq_,module,exports){
'use strict';
var Lib = _dereq_('../../lib');
var constraintMapping = _dereq_('./constraint_mapping');
var endPlus = _dereq_('./end_plus');
module.exports = function emptyPathinfo(contours, plotinfo, cd0) {
var contoursFinal = (contours.type === 'constraint') ?
constraintMapping[contours._operation](contours.value) :
contours;
var cs = contoursFinal.size;
var pathinfo = [];
var end = endPlus(contoursFinal);
var carpet = cd0.trace._carpetTrace;
var basePathinfo = carpet ? {
// store axes so we can convert to px
xaxis: carpet.aaxis,
yaxis: carpet.baxis,
// full data arrays to use for interpolation
x: cd0.a,
y: cd0.b
} : {
xaxis: plotinfo.xaxis,
yaxis: plotinfo.yaxis,
x: cd0.x,
y: cd0.y
};
for(var ci = contoursFinal.start; ci < end; ci += cs) {
pathinfo.push(Lib.extendFlat({
level: ci,
// all the cells with nontrivial marching index
crossings: {},
// starting points on the edges of the lattice for each contour
starts: [],
// all unclosed paths (may have less items than starts,
// if a path is closed by rounding)
edgepaths: [],
// all closed paths
paths: [],
z: cd0.z,
smoothing: cd0.trace.line.smoothing
}, basePathinfo));
if(pathinfo.length > 1000) {
Lib.warn('Too many contours, clipping at 1000', contours);
break;
}
}
return pathinfo;
};
},{"../../lib":503,"./constraint_mapping":741,"./end_plus":746}],746:[function(_dereq_,module,exports){
'use strict';
/*
* tiny helper to move the end of the contours a little to prevent
* losing the last contour to rounding errors
*/
module.exports = function endPlus(contours) {
return contours.end + contours.size / 1e6;
};
},{}],747:[function(_dereq_,module,exports){
'use strict';
var Lib = _dereq_('../../lib');
var constants = _dereq_('./constants');
module.exports = function findAllPaths(pathinfo, xtol, ytol) {
var cnt,
startLoc,
i,
pi,
j;
// Default just passes these values through as they were before:
xtol = xtol || 0.01;
ytol = ytol || 0.01;
for(i = 0; i < pathinfo.length; i++) {
pi = pathinfo[i];
for(j = 0; j < pi.starts.length; j++) {
startLoc = pi.starts[j];
makePath(pi, startLoc, 'edge', xtol, ytol);
}
cnt = 0;
while(Object.keys(pi.crossings).length && cnt < 10000) {
cnt++;
startLoc = Object.keys(pi.crossings)[0].split(',').map(Number);
makePath(pi, startLoc, undefined, xtol, ytol);
}
if(cnt === 10000) Lib.log('Infinite loop in contour?');
}
};
function equalPts(pt1, pt2, xtol, ytol) {
return Math.abs(pt1[0] - pt2[0]) < xtol &&
Math.abs(pt1[1] - pt2[1]) < ytol;
}
// distance in index units - uses the 3rd and 4th items in points
function ptDist(pt1, pt2) {
var dx = pt1[2] - pt2[2];
var dy = pt1[3] - pt2[3];
return Math.sqrt(dx * dx + dy * dy);
}
function makePath(pi, loc, edgeflag, xtol, ytol) {
var locStr = loc.join(',');
var mi = pi.crossings[locStr];
var marchStep = getStartStep(mi, edgeflag, loc);
// start by going backward a half step and finding the crossing point
var pts = [getInterpPx(pi, loc, [-marchStep[0], -marchStep[1]])];
var m = pi.z.length;
var n = pi.z[0].length;
var startLoc = loc.slice();
var startStep = marchStep.slice();
var cnt;
// now follow the path
for(cnt = 0; cnt < 10000; cnt++) { // just to avoid infinite loops
if(mi > 20) {
mi = constants.CHOOSESADDLE[mi][(marchStep[0] || marchStep[1]) < 0 ? 0 : 1];
pi.crossings[locStr] = constants.SADDLEREMAINDER[mi];
} else {
delete pi.crossings[locStr];
}
marchStep = constants.NEWDELTA[mi];
if(!marchStep) {
Lib.log('Found bad marching index:', mi, loc, pi.level);
break;
}
// find the crossing a half step forward, and then take the full step
pts.push(getInterpPx(pi, loc, marchStep));
loc[0] += marchStep[0];
loc[1] += marchStep[1];
locStr = loc.join(',');
// don't include the same point multiple times
if(equalPts(pts[pts.length - 1], pts[pts.length - 2], xtol, ytol)) pts.pop();
var atEdge = (marchStep[0] && (loc[0] < 0 || loc[0] > n - 2)) ||
(marchStep[1] && (loc[1] < 0 || loc[1] > m - 2));
var closedLoop = loc[0] === startLoc[0] && loc[1] === startLoc[1] &&
marchStep[0] === startStep[0] && marchStep[1] === startStep[1];
// have we completed a loop, or reached an edge?
if((closedLoop) || (edgeflag && atEdge)) break;
mi = pi.crossings[locStr];
}
if(cnt === 10000) {
Lib.log('Infinite loop in contour?');
}
var closedpath = equalPts(pts[0], pts[pts.length - 1], xtol, ytol);
var totaldist = 0;
var distThresholdFactor = 0.2 * pi.smoothing;
var alldists = [];
var cropstart = 0;
var distgroup, cnt2, cnt3, newpt, ptcnt, ptavg, thisdist,
i, j, edgepathi, edgepathj;
/*
* Check for points that are too close together (<1/5 the average dist
* *in grid index units* (important for log axes and nonuniform grids),
* less if less smoothed) and just take the center (or avg of center 2).
* This cuts down on funny behavior when a point is very close to a
* contour level.
*/
for(cnt = 1; cnt < pts.length; cnt++) {
thisdist = ptDist(pts[cnt], pts[cnt - 1]);
totaldist += thisdist;
alldists.push(thisdist);
}
var distThreshold = totaldist / alldists.length * distThresholdFactor;
function getpt(i) { return pts[i % pts.length]; }
for(cnt = pts.length - 2; cnt >= cropstart; cnt--) {
distgroup = alldists[cnt];
if(distgroup < distThreshold) {
cnt3 = 0;
for(cnt2 = cnt - 1; cnt2 >= cropstart; cnt2--) {
if(distgroup + alldists[cnt2] < distThreshold) {
distgroup += alldists[cnt2];
} else break;
}
// closed path with close points wrapping around the boundary?
if(closedpath && cnt === pts.length - 2) {
for(cnt3 = 0; cnt3 < cnt2; cnt3++) {
if(distgroup + alldists[cnt3] < distThreshold) {
distgroup += alldists[cnt3];
} else break;
}
}
ptcnt = cnt - cnt2 + cnt3 + 1;
ptavg = Math.floor((cnt + cnt2 + cnt3 + 2) / 2);
// either endpoint included: keep the endpoint
if(!closedpath && cnt === pts.length - 2) newpt = pts[pts.length - 1];
else if(!closedpath && cnt2 === -1) newpt = pts[0];
// odd # of points - just take the central one
else if(ptcnt % 2) newpt = getpt(ptavg);
// even # of pts - average central two
else {
newpt = [(getpt(ptavg)[0] + getpt(ptavg + 1)[0]) / 2,
(getpt(ptavg)[1] + getpt(ptavg + 1)[1]) / 2];
}
pts.splice(cnt2 + 1, cnt - cnt2 + 1, newpt);
cnt = cnt2 + 1;
if(cnt3) cropstart = cnt3;
if(closedpath) {
if(cnt === pts.length - 2) pts[cnt3] = pts[pts.length - 1];
else if(cnt === 0) pts[pts.length - 1] = pts[0];
}
}
}
pts.splice(0, cropstart);
// done with the index parts - remove them so path generation works right
// because it depends on only having [xpx, ypx]
for(cnt = 0; cnt < pts.length; cnt++) pts[cnt].length = 2;
// don't return single-point paths (ie all points were the same
// so they got deleted?)
if(pts.length < 2) return;
else if(closedpath) {
pts.pop();
pi.paths.push(pts);
} else {
if(!edgeflag) {
Lib.log('Unclosed interior contour?',
pi.level, startLoc.join(','), pts.join('L'));
}
// edge path - does it start where an existing edge path ends, or vice versa?
var merged = false;
for(i = 0; i < pi.edgepaths.length; i++) {
edgepathi = pi.edgepaths[i];
if(!merged && equalPts(edgepathi[0], pts[pts.length - 1], xtol, ytol)) {
pts.pop();
merged = true;
// now does it ALSO meet the end of another (or the same) path?
var doublemerged = false;
for(j = 0; j < pi.edgepaths.length; j++) {
edgepathj = pi.edgepaths[j];
if(equalPts(edgepathj[edgepathj.length - 1], pts[0], xtol, ytol)) {
doublemerged = true;
pts.shift();
pi.edgepaths.splice(i, 1);
if(j === i) {
// the path is now closed
pi.paths.push(pts.concat(edgepathj));
} else {
if(j > i) j--;
pi.edgepaths[j] = edgepathj.concat(pts, edgepathi);
}
break;
}
}
if(!doublemerged) {
pi.edgepaths[i] = pts.concat(edgepathi);
}
}
}
for(i = 0; i < pi.edgepaths.length; i++) {
if(merged) break;
edgepathi = pi.edgepaths[i];
if(equalPts(edgepathi[edgepathi.length - 1], pts[0], xtol, ytol)) {
pts.shift();
pi.edgepaths[i] = edgepathi.concat(pts);
merged = true;
}
}
if(!merged) pi.edgepaths.push(pts);
}
}
// special function to get the marching step of the
// first point in the path (leading to loc)
function getStartStep(mi, edgeflag, loc) {
var dx = 0;
var dy = 0;
if(mi > 20 && edgeflag) {
// these saddles start at +/- x
if(mi === 208 || mi === 1114) {
// if we're starting at the left side, we must be going right
dx = loc[0] === 0 ? 1 : -1;
} else {
// if we're starting at the bottom, we must be going up
dy = loc[1] === 0 ? 1 : -1;
}
} else if(constants.BOTTOMSTART.indexOf(mi) !== -1) dy = 1;
else if(constants.LEFTSTART.indexOf(mi) !== -1) dx = 1;
else if(constants.TOPSTART.indexOf(mi) !== -1) dy = -1;
else dx = -1;
return [dx, dy];
}
/*
* Find the pixel coordinates of a particular crossing
*
* @param {object} pi: the pathinfo object at this level
* @param {array} loc: the grid index [x, y] of the crossing
* @param {array} step: the direction [dx, dy] we're moving on the grid
*
* @return {array} [xpx, ypx, xi, yi]: the first two are the pixel location,
* the next two are the interpolated grid indices, which we use for
* distance calculations to delete points that are too close together.
* This is important when the grid is nonuniform (and most dramatically when
* we're on log axes and include invalid (0 or negative) values.
* It's crucial to delete these extra two before turning an array of these
* points into a path, because those routines require length-2 points.
*/
function getInterpPx(pi, loc, step) {
var locx = loc[0] + Math.max(step[0], 0);
var locy = loc[1] + Math.max(step[1], 0);
var zxy = pi.z[locy][locx];
var xa = pi.xaxis;
var ya = pi.yaxis;
// Interpolate in linear space, then convert to pixel
if(step[1]) {
var dx = (pi.level - zxy) / (pi.z[locy][locx + 1] - zxy);
// Interpolate, but protect against NaN linear values for log axis (dx will equal 1 or 0)
var dxl =
(dx !== 1 ? (1 - dx) * xa.c2l(pi.x[locx]) : 0) +
(dx !== 0 ? dx * xa.c2l(pi.x[locx + 1]) : 0);
return [xa.c2p(xa.l2c(dxl), true),
ya.c2p(pi.y[locy], true),
locx + dx, locy];
} else {
var dy = (pi.level - zxy) / (pi.z[locy + 1][locx] - zxy);
var dyl =
(dy !== 1 ? (1 - dy) * ya.c2l(pi.y[locy]) : 0) +
(dy !== 0 ? dy * ya.c2l(pi.y[locy + 1]) : 0);
return [xa.c2p(pi.x[locx], true),
ya.c2p(ya.l2c(dyl), true),
locx, locy + dy];
}
}
},{"../../lib":503,"./constants":739}],748:[function(_dereq_,module,exports){
'use strict';
var Color = _dereq_('../../components/color');
var heatmapHoverPoints = _dereq_('../heatmap/hover');
module.exports = function hoverPoints(pointData, xval, yval, hovermode, opts) {
if(!opts) opts = {};
opts.isContour = true;
var hoverData = heatmapHoverPoints(pointData, xval, yval, hovermode, opts);
if(hoverData) {
hoverData.forEach(function(hoverPt) {
var trace = hoverPt.trace;
if(trace.contours.type === 'constraint') {
if(trace.fillcolor && Color.opacity(trace.fillcolor)) {
hoverPt.color = Color.addOpacity(trace.fillcolor, 1);
} else if(trace.contours.showlines && Color.opacity(trace.line.color)) {
hoverPt.color = Color.addOpacity(trace.line.color, 1);
}
}
});
}
return hoverData;
};
},{"../../components/color":366,"../heatmap/hover":799}],749:[function(_dereq_,module,exports){
'use strict';
module.exports = {
attributes: _dereq_('./attributes'),
supplyDefaults: _dereq_('./defaults'),
calc: _dereq_('./calc'),
plot: _dereq_('./plot').plot,
style: _dereq_('./style'),
colorbar: _dereq_('./colorbar'),
hoverPoints: _dereq_('./hover'),
moduleType: 'trace',
name: 'contour',
basePlotModule: _dereq_('../../plots/cartesian'),
categories: ['cartesian', 'svg', '2dMap', 'contour', 'showLegend'],
meta: {
}
};
},{"../../plots/cartesian":568,"./attributes":735,"./calc":736,"./colorbar":738,"./defaults":744,"./hover":748,"./plot":753,"./style":755}],750:[function(_dereq_,module,exports){
'use strict';
var Lib = _dereq_('../../lib');
module.exports = function handleLabelDefaults(coerce, layout, lineColor, opts) {
if(!opts) opts = {};
var showLabels = coerce('contours.showlabels');
if(showLabels) {
var globalFont = layout.font;
Lib.coerceFont(coerce, 'contours.labelfont', {
family: globalFont.family,
size: globalFont.size,
color: lineColor
});
coerce('contours.labelformat');
}
if(opts.hasHover !== false) coerce('zhoverformat');
};
},{"../../lib":503}],751:[function(_dereq_,module,exports){
'use strict';
var d3 = _dereq_('@plotly/d3');
var Colorscale = _dereq_('../../components/colorscale');
var endPlus = _dereq_('./end_plus');
module.exports = function makeColorMap(trace) {
var contours = trace.contours;
var start = contours.start;
var end = endPlus(contours);
var cs = contours.size || 1;
var nc = Math.floor((end - start) / cs) + 1;
var extra = contours.coloring === 'lines' ? 0 : 1;
var cOpts = Colorscale.extractOpts(trace);
if(!isFinite(cs)) {
cs = 1;
nc = 1;
}
var scl = cOpts.reversescale ?
Colorscale.flipScale(cOpts.colorscale) :
cOpts.colorscale;
var len = scl.length;
var domain = new Array(len);
var range = new Array(len);
var si, i;
if(contours.coloring === 'heatmap') {
var zmin0 = cOpts.min;
var zmax0 = cOpts.max;
for(i = 0; i < len; i++) {
si = scl[i];
domain[i] = si[0] * (zmax0 - zmin0) + zmin0;
range[i] = si[1];
}
// do the contours extend beyond the colorscale?
// if so, extend the colorscale with constants
var zRange = d3.extent([
zmin0,
zmax0,
contours.start,
contours.start + cs * (nc - 1)
]);
var zmin = zRange[zmin0 < zmax0 ? 0 : 1];
var zmax = zRange[zmin0 < zmax0 ? 1 : 0];
if(zmin !== zmin0) {
domain.splice(0, 0, zmin);
range.splice(0, 0, range[0]);
}
if(zmax !== zmax0) {
domain.push(zmax);
range.push(range[range.length - 1]);
}
} else {
for(i = 0; i < len; i++) {
si = scl[i];
domain[i] = (si[0] * (nc + extra - 1) - (extra / 2)) * cs + start;
range[i] = si[1];
}
}
return Colorscale.makeColorScaleFunc(
{domain: domain, range: range},
{noNumericCheck: true}
);
};
},{"../../components/colorscale":378,"./end_plus":746,"@plotly/d3":58}],752:[function(_dereq_,module,exports){
'use strict';
var constants = _dereq_('./constants');
// Calculate all the marching indices, for ALL levels at once.
// since we want to be exhaustive we'll check for contour crossings
// at every intersection, rather than just following a path
// TODO: shorten the inner loop to only the relevant levels
module.exports = function makeCrossings(pathinfo) {
var z = pathinfo[0].z;
var m = z.length;
var n = z[0].length; // we already made sure z isn't ragged in interp2d
var twoWide = m === 2 || n === 2;
var xi;
var yi;
var startIndices;
var ystartIndices;
var label;
var corners;
var mi;
var pi;
var i;
for(yi = 0; yi < m - 1; yi++) {
ystartIndices = [];
if(yi === 0) ystartIndices = ystartIndices.concat(constants.BOTTOMSTART);
if(yi === m - 2) ystartIndices = ystartIndices.concat(constants.TOPSTART);
for(xi = 0; xi < n - 1; xi++) {
startIndices = ystartIndices.slice();
if(xi === 0) startIndices = startIndices.concat(constants.LEFTSTART);
if(xi === n - 2) startIndices = startIndices.concat(constants.RIGHTSTART);
label = xi + ',' + yi;
corners = [[z[yi][xi], z[yi][xi + 1]],
[z[yi + 1][xi], z[yi + 1][xi + 1]]];
for(i = 0; i < pathinfo.length; i++) {
pi = pathinfo[i];
mi = getMarchingIndex(pi.level, corners);
if(!mi) continue;
pi.crossings[label] = mi;
if(startIndices.indexOf(mi) !== -1) {
pi.starts.push([xi, yi]);
if(twoWide && startIndices.indexOf(mi,
startIndices.indexOf(mi) + 1) !== -1) {
// the same square has starts from opposite sides
// it's not possible to have starts on opposite edges
// of a corner, only a start and an end...
// but if the array is only two points wide (either way)
// you can have starts on opposite sides.
pi.starts.push([xi, yi]);
}
}
}
}
}
};
// modified marching squares algorithm,
// so we disambiguate the saddle points from the start
// and we ignore the cases with no crossings
// the index I'm using is based on:
// http://en.wikipedia.org/wiki/Marching_squares
// except that the saddles bifurcate and I represent them
// as the decimal combination of the two appropriate
// non-saddle indices
function getMarchingIndex(val, corners) {
var mi = (corners[0][0] > val ? 0 : 1) +
(corners[0][1] > val ? 0 : 2) +
(corners[1][1] > val ? 0 : 4) +
(corners[1][0] > val ? 0 : 8);
if(mi === 5 || mi === 10) {
var avg = (corners[0][0] + corners[0][1] +
corners[1][0] + corners[1][1]) / 4;
// two peaks with a big valley
if(val > avg) return (mi === 5) ? 713 : 1114;
// two valleys with a big ridge
return (mi === 5) ? 104 : 208;
}
return (mi === 15) ? 0 : mi;
}
},{"./constants":739}],753:[function(_dereq_,module,exports){
'use strict';
var d3 = _dereq_('@plotly/d3');
var Lib = _dereq_('../../lib');
var Drawing = _dereq_('../../components/drawing');
var Colorscale = _dereq_('../../components/colorscale');
var svgTextUtils = _dereq_('../../lib/svg_text_utils');
var Axes = _dereq_('../../plots/cartesian/axes');
var setConvert = _dereq_('../../plots/cartesian/set_convert');
var heatmapPlot = _dereq_('../heatmap/plot');
var makeCrossings = _dereq_('./make_crossings');
var findAllPaths = _dereq_('./find_all_paths');
var emptyPathinfo = _dereq_('./empty_pathinfo');
var convertToConstraints = _dereq_('./convert_to_constraints');
var closeBoundaries = _dereq_('./close_boundaries');
var constants = _dereq_('./constants');
var costConstants = constants.LABELOPTIMIZER;
exports.plot = function plot(gd, plotinfo, cdcontours, contourLayer) {
var xa = plotinfo.xaxis;
var ya = plotinfo.yaxis;
Lib.makeTraceGroups(contourLayer, cdcontours, 'contour').each(function(cd) {
var plotGroup = d3.select(this);
var cd0 = cd[0];
var trace = cd0.trace;
var x = cd0.x;
var y = cd0.y;
var contours = trace.contours;
var pathinfo = emptyPathinfo(contours, plotinfo, cd0);
// use a heatmap to fill - draw it behind the lines
var heatmapColoringLayer = Lib.ensureSingle(plotGroup, 'g', 'heatmapcoloring');
var cdheatmaps = [];
if(contours.coloring === 'heatmap') {
cdheatmaps = [cd];
}
heatmapPlot(gd, plotinfo, cdheatmaps, heatmapColoringLayer);
makeCrossings(pathinfo);
findAllPaths(pathinfo);
var leftedge = xa.c2p(x[0], true);
var rightedge = xa.c2p(x[x.length - 1], true);
var bottomedge = ya.c2p(y[0], true);
var topedge = ya.c2p(y[y.length - 1], true);
var perimeter = [
[leftedge, topedge],
[rightedge, topedge],
[rightedge, bottomedge],
[leftedge, bottomedge]
];
var fillPathinfo = pathinfo;
if(contours.type === 'constraint') {
// N.B. this also mutates pathinfo
fillPathinfo = convertToConstraints(pathinfo, contours._operation);
}
// draw everything
makeBackground(plotGroup, perimeter, contours);
makeFills(plotGroup, fillPathinfo, perimeter, contours);
makeLinesAndLabels(plotGroup, pathinfo, gd, cd0, contours);
clipGaps(plotGroup, plotinfo, gd, cd0, perimeter);
});
};
function makeBackground(plotgroup, perimeter, contours) {
var bggroup = Lib.ensureSingle(plotgroup, 'g', 'contourbg');
var bgfill = bggroup.selectAll('path')
.data(contours.coloring === 'fill' ? [0] : []);
bgfill.enter().append('path');
bgfill.exit().remove();
bgfill
.attr('d', 'M' + perimeter.join('L') + 'Z')
.style('stroke', 'none');
}
function makeFills(plotgroup, pathinfo, perimeter, contours) {
var hasFills = contours.coloring === 'fill' || (contours.type === 'constraint' && contours._operation !== '=');
var boundaryPath = 'M' + perimeter.join('L') + 'Z';
// fills prefixBoundary in pathinfo items
if(hasFills) {
closeBoundaries(pathinfo, contours);
}
var fillgroup = Lib.ensureSingle(plotgroup, 'g', 'contourfill');
var fillitems = fillgroup.selectAll('path').data(hasFills ? pathinfo : []);
fillitems.enter().append('path');
fillitems.exit().remove();
fillitems.each(function(pi) {
// join all paths for this level together into a single path
// first follow clockwise around the perimeter to close any open paths
// if the whole perimeter is above this level, start with a path
// enclosing the whole thing. With all that, the parity should mean
// that we always fill everything above the contour, nothing below
var fullpath = (pi.prefixBoundary ? boundaryPath : '') +
joinAllPaths(pi, perimeter);
if(!fullpath) {
d3.select(this).remove();
} else {
d3.select(this)
.attr('d', fullpath)
.style('stroke', 'none');
}
});
}
function joinAllPaths(pi, perimeter) {
var fullpath = '';
var i = 0;
var startsleft = pi.edgepaths.map(function(v, i) { return i; });
var newloop = true;
var endpt;
var newendpt;
var cnt;
var nexti;
var possiblei;
var addpath;
function istop(pt) { return Math.abs(pt[1] - perimeter[0][1]) < 0.01; }
function isbottom(pt) { return Math.abs(pt[1] - perimeter[2][1]) < 0.01; }
function isleft(pt) { return Math.abs(pt[0] - perimeter[0][0]) < 0.01; }
function isright(pt) { return Math.abs(pt[0] - perimeter[2][0]) < 0.01; }
while(startsleft.length) {
addpath = Drawing.smoothopen(pi.edgepaths[i], pi.smoothing);
fullpath += newloop ? addpath : addpath.replace(/^M/, 'L');
startsleft.splice(startsleft.indexOf(i), 1);
endpt = pi.edgepaths[i][pi.edgepaths[i].length - 1];
nexti = -1;
// now loop through sides, moving our endpoint until we find a new start
for(cnt = 0; cnt < 4; cnt++) { // just to prevent infinite loops
if(!endpt) {
Lib.log('Missing end?', i, pi);
break;
}
if(istop(endpt) && !isright(endpt)) newendpt = perimeter[1]; // right top
else if(isleft(endpt)) newendpt = perimeter[0]; // left top
else if(isbottom(endpt)) newendpt = perimeter[3]; // right bottom
else if(isright(endpt)) newendpt = perimeter[2]; // left bottom
for(possiblei = 0; possiblei < pi.edgepaths.length; possiblei++) {
var ptNew = pi.edgepaths[possiblei][0];
// is ptNew on the (horz. or vert.) segment from endpt to newendpt?
if(Math.abs(endpt[0] - newendpt[0]) < 0.01) {
if(Math.abs(endpt[0] - ptNew[0]) < 0.01 &&
(ptNew[1] - endpt[1]) * (newendpt[1] - ptNew[1]) >= 0) {
newendpt = ptNew;
nexti = possiblei;
}
} else if(Math.abs(endpt[1] - newendpt[1]) < 0.01) {
if(Math.abs(endpt[1] - ptNew[1]) < 0.01 &&
(ptNew[0] - endpt[0]) * (newendpt[0] - ptNew[0]) >= 0) {
newendpt = ptNew;
nexti = possiblei;
}
} else {
Lib.log('endpt to newendpt is not vert. or horz.',
endpt, newendpt, ptNew);
}
}
endpt = newendpt;
if(nexti >= 0) break;
fullpath += 'L' + newendpt;
}
if(nexti === pi.edgepaths.length) {
Lib.log('unclosed perimeter path');
break;
}
i = nexti;
// if we closed back on a loop we already included,
// close it and start a new loop
newloop = (startsleft.indexOf(i) === -1);
if(newloop) {
i = startsleft[0];
fullpath += 'Z';
}
}
// finally add the interior paths
for(i = 0; i < pi.paths.length; i++) {
fullpath += Drawing.smoothclosed(pi.paths[i], pi.smoothing);
}
return fullpath;
}
function makeLinesAndLabels(plotgroup, pathinfo, gd, cd0, contours) {
var lineContainer = Lib.ensureSingle(plotgroup, 'g', 'contourlines');
var showLines = contours.showlines !== false;
var showLabels = contours.showlabels;
var clipLinesForLabels = showLines && showLabels;
// Even if we're not going to show lines, we need to create them
// if we're showing labels, because the fill paths include the perimeter
// so can't be used to position the labels correctly.
// In this case we'll remove the lines after making the labels.
var linegroup = exports.createLines(lineContainer, showLines || showLabels, pathinfo);
var lineClip = exports.createLineClip(lineContainer, clipLinesForLabels, gd, cd0.trace.uid);
var labelGroup = plotgroup.selectAll('g.contourlabels')
.data(showLabels ? [0] : []);
labelGroup.exit().remove();
labelGroup.enter().append('g')
.classed('contourlabels', true);
if(showLabels) {
var labelClipPathData = [];
var labelData = [];
// invalidate the getTextLocation cache in case paths changed
Lib.clearLocationCache();
var contourFormat = exports.labelFormatter(gd, cd0);
var dummyText = Drawing.tester.append('text')
.attr('data-notex', 1)
.call(Drawing.font, contours.labelfont);
var xa = pathinfo[0].xaxis;
var ya = pathinfo[0].yaxis;
var xLen = xa._length;
var yLen = ya._length;
var xRng = xa.range;
var yRng = ya.range;
var xMin = Lib.aggNums(Math.min, null, cd0.x);
var xMax = Lib.aggNums(Math.max, null, cd0.x);
var yMin = Lib.aggNums(Math.min, null, cd0.y);
var yMax = Lib.aggNums(Math.max, null, cd0.y);
var x0 = Math.max(xa.c2p(xMin, true), 0);
var x1 = Math.min(xa.c2p(xMax, true), xLen);
var y0 = Math.max(ya.c2p(yMax, true), 0);
var y1 = Math.min(ya.c2p(yMin, true), yLen);
// visible bounds of the contour trace (and the midpoints, to
// help with cost calculations)
var bounds = {};
if(xRng[0] < xRng[1]) {
bounds.left = x0;
bounds.right = x1;
} else {
bounds.left = x1;
bounds.right = x0;
}
if(yRng[0] < yRng[1]) {
bounds.top = y0;
bounds.bottom = y1;
} else {
bounds.top = y1;
bounds.bottom = y0;
}
bounds.middle = (bounds.top + bounds.bottom) / 2;
bounds.center = (bounds.left + bounds.right) / 2;
labelClipPathData.push([
[bounds.left, bounds.top],
[bounds.right, bounds.top],
[bounds.right, bounds.bottom],
[bounds.left, bounds.bottom]
]);
var plotDiagonal = Math.sqrt(xLen * xLen + yLen * yLen);
// the path length to use to scale the number of labels to draw:
var normLength = constants.LABELDISTANCE * plotDiagonal /
Math.max(1, pathinfo.length / constants.LABELINCREASE);
linegroup.each(function(d) {
var textOpts = exports.calcTextOpts(d.level, contourFormat, dummyText, gd);
d3.select(this).selectAll('path').each(function() {
var path = this;
var pathBounds = Lib.getVisibleSegment(path, bounds, textOpts.height / 2);
if(!pathBounds) return;
if(pathBounds.len < (textOpts.width + textOpts.height) * constants.LABELMIN) return;
var maxLabels = Math.min(Math.ceil(pathBounds.len / normLength),
constants.LABELMAX);
for(var i = 0; i < maxLabels; i++) {
var loc = exports.findBestTextLocation(path, pathBounds, textOpts,
labelData, bounds);
if(!loc) break;
exports.addLabelData(loc, textOpts, labelData, labelClipPathData);
}
});
});
dummyText.remove();
exports.drawLabels(labelGroup, labelData, gd, lineClip,
clipLinesForLabels ? labelClipPathData : null);
}
if(showLabels && !showLines) linegroup.remove();
}
exports.createLines = function(lineContainer, makeLines, pathinfo) {
var smoothing = pathinfo[0].smoothing;
var linegroup = lineContainer.selectAll('g.contourlevel')
.data(makeLines ? pathinfo : []);
linegroup.exit().remove();
linegroup.enter().append('g')
.classed('contourlevel', true);
if(makeLines) {
// pedgepaths / ppaths are used by contourcarpet, for the paths transformed from a/b to x/y
// edgepaths / paths are used by contour since it's in x/y from the start
var opencontourlines = linegroup.selectAll('path.openline')
.data(function(d) { return d.pedgepaths || d.edgepaths; });
opencontourlines.exit().remove();
opencontourlines.enter().append('path')
.classed('openline', true);
opencontourlines
.attr('d', function(d) {
return Drawing.smoothopen(d, smoothing);
})
.style('stroke-miterlimit', 1)
.style('vector-effect', 'non-scaling-stroke');
var closedcontourlines = linegroup.selectAll('path.closedline')
.data(function(d) { return d.ppaths || d.paths; });
closedcontourlines.exit().remove();
closedcontourlines.enter().append('path')
.classed('closedline', true);
closedcontourlines
.attr('d', function(d) {
return Drawing.smoothclosed(d, smoothing);
})
.style('stroke-miterlimit', 1)
.style('vector-effect', 'non-scaling-stroke');
}
return linegroup;
};
exports.createLineClip = function(lineContainer, clipLinesForLabels, gd, uid) {
var clips = gd._fullLayout._clips;
var clipId = clipLinesForLabels ? ('clipline' + uid) : null;
var lineClip = clips.selectAll('#' + clipId)
.data(clipLinesForLabels ? [0] : []);
lineClip.exit().remove();
lineClip.enter().append('clipPath')
.classed('contourlineclip', true)
.attr('id', clipId);
Drawing.setClipUrl(lineContainer, clipId, gd);
return lineClip;
};
exports.labelFormatter = function(gd, cd0) {
var fullLayout = gd._fullLayout;
var trace = cd0.trace;
var contours = trace.contours;
var formatAxis = {
type: 'linear',
_id: 'ycontour',
showexponent: 'all',
exponentformat: 'B'
};
if(contours.labelformat) {
formatAxis.tickformat = contours.labelformat;
setConvert(formatAxis, fullLayout);
} else {
var cOpts = Colorscale.extractOpts(trace);
if(cOpts && cOpts.colorbar && cOpts.colorbar._axis) {
formatAxis = cOpts.colorbar._axis;
} else {
if(contours.type === 'constraint') {
var value = contours.value;
if(Array.isArray(value)) {
formatAxis.range = [value[0], value[value.length - 1]];
} else formatAxis.range = [value, value];
} else {
formatAxis.range = [contours.start, contours.end];
formatAxis.nticks = (contours.end - contours.start) / contours.size;
}
if(formatAxis.range[0] === formatAxis.range[1]) {
formatAxis.range[1] += formatAxis.range[0] || 1;
}
if(!formatAxis.nticks) formatAxis.nticks = 1000;
setConvert(formatAxis, fullLayout);
Axes.prepTicks(formatAxis);
formatAxis._tmin = null;
formatAxis._tmax = null;
}
}
return function(v) { return Axes.tickText(formatAxis, v).text; };
};
exports.calcTextOpts = function(level, contourFormat, dummyText, gd) {
var text = contourFormat(level);
dummyText.text(text)
.call(svgTextUtils.convertToTspans, gd);
var el = dummyText.node();
var bBox = Drawing.bBox(el, true);
return {
text: text,
width: bBox.width,
height: bBox.height,
fontSize: +(el.style['font-size'].replace('px', '')),
level: level,
dy: (bBox.top + bBox.bottom) / 2
};
};
exports.findBestTextLocation = function(path, pathBounds, textOpts, labelData, plotBounds) {
var textWidth = textOpts.width;
var p0, dp, pMax, pMin, loc;
if(pathBounds.isClosed) {
dp = pathBounds.len / costConstants.INITIALSEARCHPOINTS;
p0 = pathBounds.min + dp / 2;
pMax = pathBounds.max;
} else {
dp = (pathBounds.len - textWidth) / (costConstants.INITIALSEARCHPOINTS + 1);
p0 = pathBounds.min + dp + textWidth / 2;
pMax = pathBounds.max - (dp + textWidth) / 2;
}
var cost = Infinity;
for(var j = 0; j < costConstants.ITERATIONS; j++) {
for(var p = p0; p < pMax; p += dp) {
var newLocation = Lib.getTextLocation(path, pathBounds.total, p, textWidth);
var newCost = locationCost(newLocation, textOpts, labelData, plotBounds);
if(newCost < cost) {
cost = newCost;
loc = newLocation;
pMin = p;
}
}
if(cost > costConstants.MAXCOST * 2) break;
// subsequent iterations just look half steps away from the
// best we found in the previous iteration
if(j) dp /= 2;
p0 = pMin - dp / 2;
pMax = p0 + dp * 1.5;
}
if(cost <= costConstants.MAXCOST) return loc;
};
/*
* locationCost: a cost function for label locations
* composed of three kinds of penalty:
* - for open paths, being close to the end of the path
* - the angle away from horizontal
* - being too close to already placed neighbors
*/
function locationCost(loc, textOpts, labelData, bounds) {
var halfWidth = textOpts.width / 2;
var halfHeight = textOpts.height / 2;
var x = loc.x;
var y = loc.y;
var theta = loc.theta;
var dx = Math.cos(theta) * halfWidth;
var dy = Math.sin(theta) * halfWidth;
// cost for being near an edge
var normX = ((x > bounds.center) ? (bounds.right - x) : (x - bounds.left)) /
(dx + Math.abs(Math.sin(theta) * halfHeight));
var normY = ((y > bounds.middle) ? (bounds.bottom - y) : (y - bounds.top)) /
(Math.abs(dy) + Math.cos(theta) * halfHeight);
if(normX < 1 || normY < 1) return Infinity;
var cost = costConstants.EDGECOST * (1 / (normX - 1) + 1 / (normY - 1));
// cost for not being horizontal
cost += costConstants.ANGLECOST * theta * theta;
// cost for being close to other labels
var x1 = x - dx;
var y1 = y - dy;
var x2 = x + dx;
var y2 = y + dy;
for(var i = 0; i < labelData.length; i++) {
var labeli = labelData[i];
var dxd = Math.cos(labeli.theta) * labeli.width / 2;
var dyd = Math.sin(labeli.theta) * labeli.width / 2;
var dist = Lib.segmentDistance(
x1, y1,
x2, y2,
labeli.x - dxd, labeli.y - dyd,
labeli.x + dxd, labeli.y + dyd
) * 2 / (textOpts.height + labeli.height);
var sameLevel = labeli.level === textOpts.level;
var distOffset = sameLevel ? costConstants.SAMELEVELDISTANCE : 1;
if(dist <= distOffset) return Infinity;
var distFactor = costConstants.NEIGHBORCOST *
(sameLevel ? costConstants.SAMELEVELFACTOR : 1);
cost += distFactor / (dist - distOffset);
}
return cost;
}
exports.addLabelData = function(loc, textOpts, labelData, labelClipPathData) {
var fontSize = textOpts.fontSize;
var w = textOpts.width + fontSize / 3;
var h = Math.max(0, textOpts.height - fontSize / 3);
var x = loc.x;
var y = loc.y;
var theta = loc.theta;
var sin = Math.sin(theta);
var cos = Math.cos(theta);
var rotateXY = function(dx, dy) {
return [
x + dx * cos - dy * sin,
y + dx * sin + dy * cos
];
};
var bBoxPts = [
rotateXY(-w / 2, -h / 2),
rotateXY(-w / 2, h / 2),
rotateXY(w / 2, h / 2),
rotateXY(w / 2, -h / 2)
];
labelData.push({
text: textOpts.text,
x: x,
y: y,
dy: textOpts.dy,
theta: theta,
level: textOpts.level,
width: w,
height: h
});
labelClipPathData.push(bBoxPts);
};
exports.drawLabels = function(labelGroup, labelData, gd, lineClip, labelClipPathData) {
var labels = labelGroup.selectAll('text')
.data(labelData, function(d) {
return d.text + ',' + d.x + ',' + d.y + ',' + d.theta;
});
labels.exit().remove();
labels.enter().append('text')
.attr({
'data-notex': 1,
'text-anchor': 'middle'
})
.each(function(d) {
var x = d.x + Math.sin(d.theta) * d.dy;
var y = d.y - Math.cos(d.theta) * d.dy;
d3.select(this)
.text(d.text)
.attr({
x: x,
y: y,
transform: 'rotate(' + (180 * d.theta / Math.PI) + ' ' + x + ' ' + y + ')'
})
.call(svgTextUtils.convertToTspans, gd);
});
if(labelClipPathData) {
var clipPath = '';
for(var i = 0; i < labelClipPathData.length; i++) {
clipPath += 'M' + labelClipPathData[i].join('L') + 'Z';
}
var lineClipPath = Lib.ensureSingle(lineClip, 'path', '');
lineClipPath.attr('d', clipPath);
}
};
function clipGaps(plotGroup, plotinfo, gd, cd0, perimeter) {
var trace = cd0.trace;
var clips = gd._fullLayout._clips;
var clipId = 'clip' + trace.uid;
var clipPath = clips.selectAll('#' + clipId)
.data(trace.connectgaps ? [] : [0]);
clipPath.enter().append('clipPath')
.classed('contourclip', true)
.attr('id', clipId);
clipPath.exit().remove();
if(trace.connectgaps === false) {
var clipPathInfo = {
// fraction of the way from missing to present point
// to draw the boundary.
// if you make this 1 (or 1-epsilon) then a point in
// a sea of missing data will disappear entirely.
level: 0.9,
crossings: {},
starts: [],
edgepaths: [],
paths: [],
xaxis: plotinfo.xaxis,
yaxis: plotinfo.yaxis,
x: cd0.x,
y: cd0.y,
// 0 = no data, 1 = data
z: makeClipMask(cd0),
smoothing: 0
};
makeCrossings([clipPathInfo]);
findAllPaths([clipPathInfo]);
closeBoundaries([clipPathInfo], {type: 'levels'});
var path = Lib.ensureSingle(clipPath, 'path', '');
path.attr('d',
(clipPathInfo.prefixBoundary ? 'M' + perimeter.join('L') + 'Z' : '') +
joinAllPaths(clipPathInfo, perimeter)
);
} else clipId = null;
Drawing.setClipUrl(plotGroup, clipId, gd);
}
function makeClipMask(cd0) {
var empties = cd0.trace._emptypoints;
var z = [];
var m = cd0.z.length;
var n = cd0.z[0].length;
var i;
var row = [];
var emptyPoint;
for(i = 0; i < n; i++) row.push(1);
for(i = 0; i < m; i++) z.push(row.slice());
for(i = 0; i < empties.length; i++) {
emptyPoint = empties[i];
z[emptyPoint[0]][emptyPoint[1]] = 0;
}
// save this mask to determine whether to show this data in hover
cd0.zmask = z;
return z;
}
},{"../../components/colorscale":378,"../../components/drawing":388,"../../lib":503,"../../lib/svg_text_utils":529,"../../plots/cartesian/axes":554,"../../plots/cartesian/set_convert":576,"../heatmap/plot":803,"./close_boundaries":737,"./constants":739,"./convert_to_constraints":743,"./empty_pathinfo":745,"./find_all_paths":747,"./make_crossings":752,"@plotly/d3":58}],754:[function(_dereq_,module,exports){
'use strict';
var Axes = _dereq_('../../plots/cartesian/axes');
var Lib = _dereq_('../../lib');
module.exports = function setContours(trace, vals) {
var contours = trace.contours;
// check if we need to auto-choose contour levels
if(trace.autocontour) {
// N.B. do not try to use coloraxis cmin/cmax,
// these values here are meant to remain "per-trace" for now
var zmin = trace.zmin;
var zmax = trace.zmax;
if(trace.zauto || zmin === undefined) {
zmin = Lib.aggNums(Math.min, null, vals);
}
if(trace.zauto || zmax === undefined) {
zmax = Lib.aggNums(Math.max, null, vals);
}
var dummyAx = autoContours(zmin, zmax, trace.ncontours);
contours.size = dummyAx.dtick;
contours.start = Axes.tickFirst(dummyAx);
dummyAx.range.reverse();
contours.end = Axes.tickFirst(dummyAx);
if(contours.start === zmin) contours.start += contours.size;
if(contours.end === zmax) contours.end -= contours.size;
// if you set a small ncontours, *and* the ends are exactly on zmin/zmax
// there's an edge case where start > end now. Make sure there's at least
// one meaningful contour, put it midway between the crossed values
if(contours.start > contours.end) {
contours.start = contours.end = (contours.start + contours.end) / 2;
}
// copy auto-contour info back to the source data.
// previously we copied the whole contours object back, but that had
// other info (coloring, showlines) that should be left to supplyDefaults
if(!trace._input.contours) trace._input.contours = {};
Lib.extendFlat(trace._input.contours, {
start: contours.start,
end: contours.end,
size: contours.size
});
trace._input.autocontour = true;
} else if(contours.type !== 'constraint') {
// sanity checks on manually-supplied start/end/size
var start = contours.start;
var end = contours.end;
var inputContours = trace._input.contours;
if(start > end) {
contours.start = inputContours.start = end;
end = contours.end = inputContours.end = start;
start = contours.start;
}
if(!(contours.size > 0)) {
var sizeOut;
if(start === end) sizeOut = 1;
else sizeOut = autoContours(start, end, trace.ncontours).dtick;
inputContours.size = contours.size = sizeOut;
}
}
};
/*
* autoContours: make a dummy axis object with dtick we can use
* as contours.size, and if needed we can use Axes.tickFirst
* with this axis object to calculate the start and end too
*
* start: the value to start the contours at
* end: the value to end at (must be > start)
* ncontours: max number of contours to make, like roughDTick
*
* returns: an axis object
*/
function autoContours(start, end, ncontours) {
var dummyAx = {
type: 'linear',
range: [start, end]
};
Axes.autoTicks(
dummyAx,
(end - start) / (ncontours || 15)
);
return dummyAx;
}
},{"../../lib":503,"../../plots/cartesian/axes":554}],755:[function(_dereq_,module,exports){
'use strict';
var d3 = _dereq_('@plotly/d3');
var Drawing = _dereq_('../../components/drawing');
var heatmapStyle = _dereq_('../heatmap/style');
var makeColorMap = _dereq_('./make_color_map');
module.exports = function style(gd) {
var contours = d3.select(gd).selectAll('g.contour');
contours.style('opacity', function(d) {
return d[0].trace.opacity;
});
contours.each(function(d) {
var c = d3.select(this);
var trace = d[0].trace;
var contours = trace.contours;
var line = trace.line;
var cs = contours.size || 1;
var start = contours.start;
// for contourcarpet only - is this a constraint-type contour trace?
var isConstraintType = contours.type === 'constraint';
var colorLines = !isConstraintType && contours.coloring === 'lines';
var colorFills = !isConstraintType && contours.coloring === 'fill';
var colorMap = (colorLines || colorFills) ? makeColorMap(trace) : null;
c.selectAll('g.contourlevel').each(function(d) {
d3.select(this).selectAll('path')
.call(Drawing.lineGroupStyle,
line.width,
colorLines ? colorMap(d.level) : line.color,
line.dash);
});
var labelFont = contours.labelfont;
c.selectAll('g.contourlabels text').each(function(d) {
Drawing.font(d3.select(this), {
family: labelFont.family,
size: labelFont.size,
color: labelFont.color || (colorLines ? colorMap(d.level) : line.color)
});
});
if(isConstraintType) {
c.selectAll('g.contourfill path')
.style('fill', trace.fillcolor);
} else if(colorFills) {
var firstFill;
c.selectAll('g.contourfill path')
.style('fill', function(d) {
if(firstFill === undefined) firstFill = d.level;
return colorMap(d.level + 0.5 * cs);
});
if(firstFill === undefined) firstFill = start;
c.selectAll('g.contourbg path')
.style('fill', colorMap(firstFill - 0.5 * cs));
}
});
heatmapStyle(gd);
};
},{"../../components/drawing":388,"../heatmap/style":804,"./make_color_map":751,"@plotly/d3":58}],756:[function(_dereq_,module,exports){
'use strict';
var colorscaleDefaults = _dereq_('../../components/colorscale/defaults');
var handleLabelDefaults = _dereq_('./label_defaults');
module.exports = function handleStyleDefaults(traceIn, traceOut, coerce, layout, opts) {
var coloring = coerce('contours.coloring');
var showLines;
var lineColor = '';
if(coloring === 'fill') showLines = coerce('contours.showlines');
if(showLines !== false) {
if(coloring !== 'lines') lineColor = coerce('line.color', '#000');
coerce('line.width', 0.5);
coerce('line.dash');
}
if(coloring !== 'none') {
// plots/plots always coerces showlegend to true, but in this case
// we default to false and (by default) show a colorbar instead
if(traceIn.showlegend !== true) traceOut.showlegend = false;
traceOut._dfltShowLegend = false;
colorscaleDefaults(
traceIn, traceOut, layout, coerce, {prefix: '', cLetter: 'z'}
);
}
coerce('line.smoothing');
handleLabelDefaults(coerce, layout, lineColor, opts);
};
},{"../../components/colorscale/defaults":376,"./label_defaults":750}],757:[function(_dereq_,module,exports){
'use strict';
var heatmapAttrs = _dereq_('../heatmap/attributes');
var contourAttrs = _dereq_('../contour/attributes');
var colorScaleAttrs = _dereq_('../../components/colorscale/attributes');
var extendFlat = _dereq_('../../lib/extend').extendFlat;
var contourContourAttrs = contourAttrs.contours;
module.exports = extendFlat({
carpet: {
valType: 'string',
editType: 'calc',
},
z: heatmapAttrs.z,
a: heatmapAttrs.x,
a0: heatmapAttrs.x0,
da: heatmapAttrs.dx,
b: heatmapAttrs.y,
b0: heatmapAttrs.y0,
db: heatmapAttrs.dy,
text: heatmapAttrs.text,
hovertext: heatmapAttrs.hovertext,
transpose: heatmapAttrs.transpose,
atype: heatmapAttrs.xtype,
btype: heatmapAttrs.ytype,
fillcolor: contourAttrs.fillcolor,
autocontour: contourAttrs.autocontour,
ncontours: contourAttrs.ncontours,
contours: {
type: contourContourAttrs.type,
start: contourContourAttrs.start,
end: contourContourAttrs.end,
size: contourContourAttrs.size,
coloring: {
// from contourAttrs.contours.coloring but no 'heatmap' option
valType: 'enumerated',
values: ['fill', 'lines', 'none'],
dflt: 'fill',
editType: 'calc',
},
showlines: contourContourAttrs.showlines,
showlabels: contourContourAttrs.showlabels,
labelfont: contourContourAttrs.labelfont,
labelformat: contourContourAttrs.labelformat,
operation: contourContourAttrs.operation,
value: contourContourAttrs.value,
editType: 'calc',
impliedEdits: {'autocontour': false}
},
line: {
color: contourAttrs.line.color,
width: contourAttrs.line.width,
dash: contourAttrs.line.dash,
smoothing: contourAttrs.line.smoothing,
editType: 'plot'
},
transforms: undefined
},
colorScaleAttrs('', {
cLetter: 'z',
autoColorDflt: false
})
);
},{"../../components/colorscale/attributes":373,"../../lib/extend":493,"../contour/attributes":735,"../heatmap/attributes":792}],758:[function(_dereq_,module,exports){
'use strict';
var colorscaleCalc = _dereq_('../../components/colorscale/calc');
var Lib = _dereq_('../../lib');
var convertColumnData = _dereq_('../heatmap/convert_column_xyz');
var clean2dArray = _dereq_('../heatmap/clean_2d_array');
var interp2d = _dereq_('../heatmap/interp2d');
var findEmpties = _dereq_('../heatmap/find_empties');
var makeBoundArray = _dereq_('../heatmap/make_bound_array');
var supplyDefaults = _dereq_('./defaults');
var lookupCarpet = _dereq_('../carpet/lookup_carpetid');
var setContours = _dereq_('../contour/set_contours');
// most is the same as heatmap calc, then adjust it
// though a few things inside heatmap calc still look for
// contour maps, because the makeBoundArray calls are too entangled
module.exports = function calc(gd, trace) {
var carpet = trace._carpetTrace = lookupCarpet(gd, trace);
if(!carpet || !carpet.visible || carpet.visible === 'legendonly') return;
if(!trace.a || !trace.b) {
// Look up the original incoming carpet data:
var carpetdata = gd.data[carpet.index];
// Look up the incoming trace data, *except* perform a shallow
// copy so that we're not actually modifying it when we use it
// to supply defaults:
var tracedata = gd.data[trace.index];
// var tracedata = extendFlat({}, gd.data[trace.index]);
// If the data is not specified
if(!tracedata.a) tracedata.a = carpetdata.a;
if(!tracedata.b) tracedata.b = carpetdata.b;
supplyDefaults(tracedata, trace, trace._defaultColor, gd._fullLayout);
}
var cd = heatmappishCalc(gd, trace);
setContours(trace, trace._z);
return cd;
};
function heatmappishCalc(gd, trace) {
// prepare the raw data
// run makeCalcdata on x and y even for heatmaps, in case of category mappings
var carpet = trace._carpetTrace;
var aax = carpet.aaxis;
var bax = carpet.baxis;
var a,
a0,
da,
b,
b0,
db,
z;
// cancel minimum tick spacings (only applies to bars and boxes)
aax._minDtick = 0;
bax._minDtick = 0;
if(Lib.isArray1D(trace.z)) convertColumnData(trace, aax, bax, 'a', 'b', ['z']);
a = trace._a = trace._a || trace.a;
b = trace._b = trace._b || trace.b;
a = a ? aax.makeCalcdata(trace, '_a') : [];
b = b ? bax.makeCalcdata(trace, '_b') : [];
a0 = trace.a0 || 0;
da = trace.da || 1;
b0 = trace.b0 || 0;
db = trace.db || 1;
z = trace._z = clean2dArray(trace._z || trace.z, trace.transpose);
trace._emptypoints = findEmpties(z);
interp2d(z, trace._emptypoints);
// create arrays of brick boundaries, to be used by autorange and heatmap.plot
var xlen = Lib.maxRowLength(z);
var xIn = trace.xtype === 'scaled' ? '' : a;
var xArray = makeBoundArray(trace, xIn, a0, da, xlen, aax);
var yIn = trace.ytype === 'scaled' ? '' : b;
var yArray = makeBoundArray(trace, yIn, b0, db, z.length, bax);
var cd0 = {
a: xArray,
b: yArray,
z: z,
};
if(trace.contours.type === 'levels' && trace.contours.coloring !== 'none') {
// auto-z and autocolorscale if applicable
colorscaleCalc(gd, trace, {
vals: z,
containerStr: '',
cLetter: 'z'
});
}
return [cd0];
}
},{"../../components/colorscale/calc":374,"../../lib":503,"../carpet/lookup_carpetid":708,"../contour/set_contours":754,"../heatmap/clean_2d_array":794,"../heatmap/convert_column_xyz":796,"../heatmap/find_empties":798,"../heatmap/interp2d":801,"../heatmap/make_bound_array":802,"./defaults":759}],759:[function(_dereq_,module,exports){
'use strict';
var Lib = _dereq_('../../lib');
var handleXYZDefaults = _dereq_('../heatmap/xyz_defaults');
var attributes = _dereq_('./attributes');
var handleConstraintDefaults = _dereq_('../contour/constraint_defaults');
var handleContoursDefaults = _dereq_('../contour/contours_defaults');
var handleStyleDefaults = _dereq_('../contour/style_defaults');
module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout) {
function coerce(attr, dflt) {
return Lib.coerce(traceIn, traceOut, attributes, attr, dflt);
}
function coerce2(attr) {
return Lib.coerce2(traceIn, traceOut, attributes, attr);
}
coerce('carpet');
// If either a or b is not present, then it's not a valid trace *unless* the carpet
// axis has the a or b values we're looking for. So if these are not found, just defer
// that decision until the calc step.
//
// NB: the calc step will modify the original data input by assigning whichever of
// a or b are missing. This is necessary because panning goes right from supplyDefaults
// to plot (skipping calc). That means on subsequent updates, this *will* need to be
// able to find a and b.
//
// The long-term proper fix is that this should perhaps use underscored attributes to
// at least modify the user input to a slightly lesser extent. Fully removing the
// input mutation is challenging. The underscore approach is not currently taken since
// it requires modification to all of the functions below that expect the coerced
// attribute name to match the property name -- except '_a' !== 'a' so that is not
// straightforward.
if(traceIn.a && traceIn.b) {
var len = handleXYZDefaults(traceIn, traceOut, coerce, layout, 'a', 'b');
if(!len) {
traceOut.visible = false;
return;
}
coerce('text');
var isConstraint = (coerce('contours.type') === 'constraint');
if(isConstraint) {
handleConstraintDefaults(traceIn, traceOut, coerce, layout, defaultColor, {hasHover: false});
} else {
handleContoursDefaults(traceIn, traceOut, coerce, coerce2);
handleStyleDefaults(traceIn, traceOut, coerce, layout, {hasHover: false});
}
} else {
traceOut._defaultColor = defaultColor;
traceOut._length = null;
}
};
},{"../../lib":503,"../contour/constraint_defaults":740,"../contour/contours_defaults":742,"../contour/style_defaults":756,"../heatmap/xyz_defaults":806,"./attributes":757}],760:[function(_dereq_,module,exports){
'use strict';
module.exports = {
attributes: _dereq_('./attributes'),
supplyDefaults: _dereq_('./defaults'),
colorbar: _dereq_('../contour/colorbar'),
calc: _dereq_('./calc'),
plot: _dereq_('./plot'),
style: _dereq_('../contour/style'),
moduleType: 'trace',
name: 'contourcarpet',
basePlotModule: _dereq_('../../plots/cartesian'),
categories: ['cartesian', 'svg', 'carpet', 'contour', 'symbols', 'showLegend', 'hasLines', 'carpetDependent', 'noHover', 'noSortingByValue'],
meta: {
}
};
},{"../../plots/cartesian":568,"../contour/colorbar":738,"../contour/style":755,"./attributes":757,"./calc":758,"./defaults":759,"./plot":761}],761:[function(_dereq_,module,exports){
'use strict';
var d3 = _dereq_('@plotly/d3');
var map1dArray = _dereq_('../carpet/map_1d_array');
var makepath = _dereq_('../carpet/makepath');
var Drawing = _dereq_('../../components/drawing');
var Lib = _dereq_('../../lib');
var makeCrossings = _dereq_('../contour/make_crossings');
var findAllPaths = _dereq_('../contour/find_all_paths');
var contourPlot = _dereq_('../contour/plot');
var constants = _dereq_('../contour/constants');
var convertToConstraints = _dereq_('../contour/convert_to_constraints');
var emptyPathinfo = _dereq_('../contour/empty_pathinfo');
var closeBoundaries = _dereq_('../contour/close_boundaries');
var lookupCarpet = _dereq_('../carpet/lookup_carpetid');
var axisAlignedLine = _dereq_('../carpet/axis_aligned_line');
module.exports = function plot(gd, plotinfo, cdcontours, contourcarpetLayer) {
var xa = plotinfo.xaxis;
var ya = plotinfo.yaxis;
Lib.makeTraceGroups(contourcarpetLayer, cdcontours, 'contour').each(function(cd) {
var plotGroup = d3.select(this);
var cd0 = cd[0];
var trace = cd0.trace;
var carpet = trace._carpetTrace = lookupCarpet(gd, trace);
var carpetcd = gd.calcdata[carpet.index][0];
if(!carpet.visible || carpet.visible === 'legendonly') return;
var a = cd0.a;
var b = cd0.b;
var contours = trace.contours;
var pathinfo = emptyPathinfo(contours, plotinfo, cd0);
var isConstraint = contours.type === 'constraint';
var operation = contours._operation;
var coloring = isConstraint ? (operation === '=' ? 'lines' : 'fill') : contours.coloring;
// Map [a, b] (data) --> [i, j] (pixels)
function ab2p(ab) {
var pt = carpet.ab2xy(ab[0], ab[1], true);
return [xa.c2p(pt[0]), ya.c2p(pt[1])];
}
// Define the perimeter in a/b coordinates:
var perimeter = [
[a[0], b[b.length - 1]],
[a[a.length - 1], b[b.length - 1]],
[a[a.length - 1], b[0]],
[a[0], b[0]]
];
// Extract the contour levels:
makeCrossings(pathinfo);
var atol = (a[a.length - 1] - a[0]) * 1e-8;
var btol = (b[b.length - 1] - b[0]) * 1e-8;
findAllPaths(pathinfo, atol, btol);
// Constraints might need to be draw inverted, which is not something contours
// handle by default since they're assumed fully opaque so that they can be
// drawn overlapping. This function flips the paths as necessary so that they're
// drawn correctly.
//
// TODO: Perhaps this should be generalized and *all* paths should be drawn as
// closed regions so that translucent contour levels would be valid.
// See: https://github.com/plotly/plotly.js/issues/1356
var fillPathinfo = pathinfo;
if(contours.type === 'constraint') {
fillPathinfo = convertToConstraints(pathinfo, operation);
}
// Map the paths in a/b coordinates to pixel coordinates:
mapPathinfo(pathinfo, ab2p);
// draw everything
// Compute the boundary path
var seg, xp, yp, i;
var segs = [];
for(i = carpetcd.clipsegments.length - 1; i >= 0; i--) {
seg = carpetcd.clipsegments[i];
xp = map1dArray([], seg.x, xa.c2p);
yp = map1dArray([], seg.y, ya.c2p);
xp.reverse();
yp.reverse();
segs.push(makepath(xp, yp, seg.bicubic));
}
var boundaryPath = 'M' + segs.join('L') + 'Z';
// Draw the baseline background fill that fills in the space behind any other
// contour levels:
makeBackground(plotGroup, carpetcd.clipsegments, xa, ya, isConstraint, coloring);
// Draw the specific contour fills. As a simplification, they're assumed to be
// fully opaque so that it's easy to draw them simply overlapping. The alternative
// would be to flip adjacent paths and draw closed paths for each level instead.
makeFills(trace, plotGroup, xa, ya, fillPathinfo, perimeter, ab2p, carpet, carpetcd, coloring, boundaryPath);
// Draw contour lines:
makeLinesAndLabels(plotGroup, pathinfo, gd, cd0, contours, plotinfo, carpet);
// Clip the boundary of the plot
Drawing.setClipUrl(plotGroup, carpet._clipPathId, gd);
});
};
function mapPathinfo(pathinfo, map) {
var i, j, k, pi, pedgepaths, ppaths, pedgepath, ppath, path;
for(i = 0; i < pathinfo.length; i++) {
pi = pathinfo[i];
pedgepaths = pi.pedgepaths = [];
ppaths = pi.ppaths = [];
for(j = 0; j < pi.edgepaths.length; j++) {
path = pi.edgepaths[j];
pedgepath = [];
for(k = 0; k < path.length; k++) {
pedgepath[k] = map(path[k]);
}
pedgepaths.push(pedgepath);
}
for(j = 0; j < pi.paths.length; j++) {
path = pi.paths[j];
ppath = [];
for(k = 0; k < path.length; k++) {
ppath[k] = map(path[k]);
}
ppaths.push(ppath);
}
}
}
function makeLinesAndLabels(plotgroup, pathinfo, gd, cd0, contours, plotinfo, carpet) {
var lineContainer = Lib.ensureSingle(plotgroup, 'g', 'contourlines');
var showLines = contours.showlines !== false;
var showLabels = contours.showlabels;
var clipLinesForLabels = showLines && showLabels;
// Even if we're not going to show lines, we need to create them
// if we're showing labels, because the fill paths include the perimeter
// so can't be used to position the labels correctly.
// In this case we'll remove the lines after making the labels.
var linegroup = contourPlot.createLines(lineContainer, showLines || showLabels, pathinfo);
var lineClip = contourPlot.createLineClip(lineContainer, clipLinesForLabels, gd, cd0.trace.uid);
var labelGroup = plotgroup.selectAll('g.contourlabels')
.data(showLabels ? [0] : []);
labelGroup.exit().remove();
labelGroup.enter().append('g')
.classed('contourlabels', true);
if(showLabels) {
var xa = plotinfo.xaxis;
var ya = plotinfo.yaxis;
var xLen = xa._length;
var yLen = ya._length;
// for simplicity use the xy box for label clipping outline.
var labelClipPathData = [[
[0, 0],
[xLen, 0],
[xLen, yLen],
[0, yLen]
]];
var labelData = [];
// invalidate the getTextLocation cache in case paths changed
Lib.clearLocationCache();
var contourFormat = contourPlot.labelFormatter(gd, cd0);
var dummyText = Drawing.tester.append('text')
.attr('data-notex', 1)
.call(Drawing.font, contours.labelfont);
// use `bounds` only to keep labels away from the x/y boundaries
// `constrainToCarpet` below ensures labels don't go off the
// carpet edges
var bounds = {
left: 0,
right: xLen,
center: xLen / 2,
top: 0,
bottom: yLen,
middle: yLen / 2
};
var plotDiagonal = Math.sqrt(xLen * xLen + yLen * yLen);
// the path length to use to scale the number of labels to draw:
var normLength = constants.LABELDISTANCE * plotDiagonal /
Math.max(1, pathinfo.length / constants.LABELINCREASE);
linegroup.each(function(d) {
var textOpts = contourPlot.calcTextOpts(d.level, contourFormat, dummyText, gd);
d3.select(this).selectAll('path').each(function(pathData) {
var path = this;
var pathBounds = Lib.getVisibleSegment(path, bounds, textOpts.height / 2);
if(!pathBounds) return;
constrainToCarpet(path, pathData, d, pathBounds, carpet, textOpts.height);
if(pathBounds.len < (textOpts.width + textOpts.height) * constants.LABELMIN) return;
var maxLabels = Math.min(Math.ceil(pathBounds.len / normLength),
constants.LABELMAX);
for(var i = 0; i < maxLabels; i++) {
var loc = contourPlot.findBestTextLocation(path, pathBounds, textOpts,
labelData, bounds);
if(!loc) break;
contourPlot.addLabelData(loc, textOpts, labelData, labelClipPathData);
}
});
});
dummyText.remove();
contourPlot.drawLabels(labelGroup, labelData, gd, lineClip,
clipLinesForLabels ? labelClipPathData : null);
}
if(showLabels && !showLines) linegroup.remove();
}
// figure out if this path goes off the edge of the carpet
// and shorten the part we call visible to keep labels away from the edge
function constrainToCarpet(path, pathData, levelData, pathBounds, carpet, textHeight) {
var pathABData;
for(var i = 0; i < levelData.pedgepaths.length; i++) {
if(pathData === levelData.pedgepaths[i]) {
pathABData = levelData.edgepaths[i];
}
}
if(!pathABData) return;
var aMin = carpet.a[0];
var aMax = carpet.a[carpet.a.length - 1];
var bMin = carpet.b[0];
var bMax = carpet.b[carpet.b.length - 1];
function getOffset(abPt, pathVector) {
var offset = 0;
var edgeVector;
var dAB = 0.1;
if(Math.abs(abPt[0] - aMin) < dAB || Math.abs(abPt[0] - aMax) < dAB) {
edgeVector = normalizeVector(carpet.dxydb_rough(abPt[0], abPt[1], dAB));
offset = Math.max(offset, textHeight * vectorTan(pathVector, edgeVector) / 2);
}
if(Math.abs(abPt[1] - bMin) < dAB || Math.abs(abPt[1] - bMax) < dAB) {
edgeVector = normalizeVector(carpet.dxyda_rough(abPt[0], abPt[1], dAB));
offset = Math.max(offset, textHeight * vectorTan(pathVector, edgeVector) / 2);
}
return offset;
}
var startVector = getUnitVector(path, 0, 1);
var endVector = getUnitVector(path, pathBounds.total, pathBounds.total - 1);
var minStart = getOffset(pathABData[0], startVector);
var maxEnd = pathBounds.total - getOffset(pathABData[pathABData.length - 1], endVector);
if(pathBounds.min < minStart) pathBounds.min = minStart;
if(pathBounds.max > maxEnd) pathBounds.max = maxEnd;
pathBounds.len = pathBounds.max - pathBounds.min;
}
function getUnitVector(path, p0, p1) {
var pt0 = path.getPointAtLength(p0);
var pt1 = path.getPointAtLength(p1);
var dx = pt1.x - pt0.x;
var dy = pt1.y - pt0.y;
var len = Math.sqrt(dx * dx + dy * dy);
return [dx / len, dy / len];
}
function normalizeVector(v) {
var len = Math.sqrt(v[0] * v[0] + v[1] * v[1]);
return [v[0] / len, v[1] / len];
}
function vectorTan(v0, v1) {
var cos = Math.abs(v0[0] * v1[0] + v0[1] * v1[1]);
var sin = Math.sqrt(1 - cos * cos);
return sin / cos;
}
function makeBackground(plotgroup, clipsegments, xaxis, yaxis, isConstraint, coloring) {
var seg, xp, yp, i;
var bggroup = Lib.ensureSingle(plotgroup, 'g', 'contourbg');
var bgfill = bggroup.selectAll('path')
.data((coloring === 'fill' && !isConstraint) ? [0] : []);
bgfill.enter().append('path');
bgfill.exit().remove();
var segs = [];
for(i = 0; i < clipsegments.length; i++) {
seg = clipsegments[i];
xp = map1dArray([], seg.x, xaxis.c2p);
yp = map1dArray([], seg.y, yaxis.c2p);
segs.push(makepath(xp, yp, seg.bicubic));
}
bgfill
.attr('d', 'M' + segs.join('L') + 'Z')
.style('stroke', 'none');
}
function makeFills(trace, plotgroup, xa, ya, pathinfo, perimeter, ab2p, carpet, carpetcd, coloring, boundaryPath) {
var hasFills = coloring === 'fill';
// fills prefixBoundary in pathinfo items
if(hasFills) {
closeBoundaries(pathinfo, trace.contours);
}
var fillgroup = Lib.ensureSingle(plotgroup, 'g', 'contourfill');
var fillitems = fillgroup.selectAll('path').data(hasFills ? pathinfo : []);
fillitems.enter().append('path');
fillitems.exit().remove();
fillitems.each(function(pi) {
// join all paths for this level together into a single path
// first follow clockwise around the perimeter to close any open paths
// if the whole perimeter is above this level, start with a path
// enclosing the whole thing. With all that, the parity should mean
// that we always fill everything above the contour, nothing below
var fullpath = (pi.prefixBoundary ? boundaryPath : '') +
joinAllPaths(trace, pi, perimeter, ab2p, carpet, carpetcd, xa, ya);
if(!fullpath) {
d3.select(this).remove();
} else {
d3.select(this)
.attr('d', fullpath)
.style('stroke', 'none');
}
});
}
function joinAllPaths(trace, pi, perimeter, ab2p, carpet, carpetcd, xa, ya) {
var i;
var fullpath = '';
var startsleft = pi.edgepaths.map(function(v, i) { return i; });
var newloop = true;
var endpt, newendpt, cnt, nexti, possiblei, addpath;
var atol = Math.abs(perimeter[0][0] - perimeter[2][0]) * 1e-4;
var btol = Math.abs(perimeter[0][1] - perimeter[2][1]) * 1e-4;
function istop(pt) { return Math.abs(pt[1] - perimeter[0][1]) < btol; }
function isbottom(pt) { return Math.abs(pt[1] - perimeter[2][1]) < btol; }
function isleft(pt) { return Math.abs(pt[0] - perimeter[0][0]) < atol; }
function isright(pt) { return Math.abs(pt[0] - perimeter[2][0]) < atol; }
function pathto(pt0, pt1) {
var i, j, segments, axis;
var path = '';
if((istop(pt0) && !isright(pt0)) || (isbottom(pt0) && !isleft(pt0))) {
axis = carpet.aaxis;
segments = axisAlignedLine(carpet, carpetcd, [pt0[0], pt1[0]], 0.5 * (pt0[1] + pt1[1]));
} else {
axis = carpet.baxis;
segments = axisAlignedLine(carpet, carpetcd, 0.5 * (pt0[0] + pt1[0]), [pt0[1], pt1[1]]);
}
for(i = 1; i < segments.length; i++) {
path += axis.smoothing ? 'C' : 'L';
for(j = 0; j < segments[i].length; j++) {
var pt = segments[i][j];
path += [xa.c2p(pt[0]), ya.c2p(pt[1])] + ' ';
}
}
return path;
}
i = 0;
endpt = null;
while(startsleft.length) {
var startpt = pi.edgepaths[i][0];
if(endpt) {
fullpath += pathto(endpt, startpt);
}
addpath = Drawing.smoothopen(pi.edgepaths[i].map(ab2p), pi.smoothing);
fullpath += newloop ? addpath : addpath.replace(/^M/, 'L');
startsleft.splice(startsleft.indexOf(i), 1);
endpt = pi.edgepaths[i][pi.edgepaths[i].length - 1];
nexti = -1;
// now loop through sides, moving our endpoint until we find a new start
for(cnt = 0; cnt < 4; cnt++) { // just to prevent infinite loops
if(!endpt) {
Lib.log('Missing end?', i, pi);
break;
}
if(istop(endpt) && !isright(endpt)) {
newendpt = perimeter[1]; // left top ---> right top
} else if(isleft(endpt)) {
newendpt = perimeter[0]; // left bottom ---> left top
} else if(isbottom(endpt)) {
newendpt = perimeter[3]; // right bottom
} else if(isright(endpt)) {
newendpt = perimeter[2]; // left bottom
}
for(possiblei = 0; possiblei < pi.edgepaths.length; possiblei++) {
var ptNew = pi.edgepaths[possiblei][0];
// is ptNew on the (horz. or vert.) segment from endpt to newendpt?
if(Math.abs(endpt[0] - newendpt[0]) < atol) {
if(Math.abs(endpt[0] - ptNew[0]) < atol &&
(ptNew[1] - endpt[1]) * (newendpt[1] - ptNew[1]) >= 0) {
newendpt = ptNew;
nexti = possiblei;
}
} else if(Math.abs(endpt[1] - newendpt[1]) < btol) {
if(Math.abs(endpt[1] - ptNew[1]) < btol &&
(ptNew[0] - endpt[0]) * (newendpt[0] - ptNew[0]) >= 0) {
newendpt = ptNew;
nexti = possiblei;
}
} else {
Lib.log('endpt to newendpt is not vert. or horz.', endpt, newendpt, ptNew);
}
}
if(nexti >= 0) break;
fullpath += pathto(endpt, newendpt);
endpt = newendpt;
}
if(nexti === pi.edgepaths.length) {
Lib.log('unclosed perimeter path');
break;
}
i = nexti;
// if we closed back on a loop we already included,
// close it and start a new loop
newloop = (startsleft.indexOf(i) === -1);
if(newloop) {
i = startsleft[0];
fullpath += pathto(endpt, newendpt) + 'Z';
endpt = null;
}
}
// finally add the interior paths
for(i = 0; i < pi.paths.length; i++) {
fullpath += Drawing.smoothclosed(pi.paths[i].map(ab2p), pi.smoothing);
}
return fullpath;
}
},{"../../components/drawing":388,"../../lib":503,"../carpet/axis_aligned_line":692,"../carpet/lookup_carpetid":708,"../carpet/makepath":709,"../carpet/map_1d_array":710,"../contour/close_boundaries":737,"../contour/constants":739,"../contour/convert_to_constraints":743,"../contour/empty_pathinfo":745,"../contour/find_all_paths":747,"../contour/make_crossings":752,"../contour/plot":753,"@plotly/d3":58}],762:[function(_dereq_,module,exports){
'use strict';
var colorScaleAttrs = _dereq_('../../components/colorscale/attributes');
var hovertemplateAttrs = _dereq_('../../plots/template_attributes').hovertemplateAttrs;
var baseAttrs = _dereq_('../../plots/attributes');
var scatterMapboxAttrs = _dereq_('../scattermapbox/attributes');
var extendFlat = _dereq_('../../lib/extend').extendFlat;
/*
* - https://docs.mapbox.com/help/tutorials/make-a-heatmap-with-mapbox-gl-js/
* - https://docs.mapbox.com/mapbox-gl-js/example/heatmap-layer/
* - https://docs.mapbox.com/mapbox-gl-js/style-spec/#layers-heatmap
* - https://blog.mapbox.com/introducing-heatmaps-in-mapbox-gl-js-71355ada9e6c
*
* Gotchas:
* - https://github.com/mapbox/mapbox-gl-js/issues/6463
* - https://github.com/mapbox/mapbox-gl-js/issues/6112
*/
/*
*
* In mathematical terms, Mapbox GL heatmaps are a bivariate (2D) kernel density
* estimation with a Gaussian kernel. It means that each data point has an area
* of “influence” around it (called a kernel) where the numerical value of
* influence (which we call density) decreases as you go further from the point.
* If we sum density values of all points in every pixel of the screen, we get a
* combined density value which we then map to a heatmap color.
*
*/
module.exports = extendFlat({
lon: scatterMapboxAttrs.lon,
lat: scatterMapboxAttrs.lat,
z: {
valType: 'data_array',
editType: 'calc',
},
radius: {
valType: 'number',
editType: 'plot',
arrayOk: true,
min: 1,
dflt: 30,
},
below: {
valType: 'string',
editType: 'plot',
},
text: scatterMapboxAttrs.text,
hovertext: scatterMapboxAttrs.hovertext,
hoverinfo: extendFlat({}, baseAttrs.hoverinfo, {
flags: ['lon', 'lat', 'z', 'text', 'name']
}),
hovertemplate: hovertemplateAttrs(),
showlegend: extendFlat({}, baseAttrs.showlegend, {dflt: false})
},
colorScaleAttrs('', {
cLetter: 'z',
editTypeOverride: 'calc'
})
);
},{"../../components/colorscale/attributes":373,"../../lib/extend":493,"../../plots/attributes":550,"../../plots/template_attributes":633,"../scattermapbox/attributes":990}],763:[function(_dereq_,module,exports){
'use strict';
var isNumeric = _dereq_('fast-isnumeric');
var isArrayOrTypedArray = _dereq_('../../lib').isArrayOrTypedArray;
var BADNUM = _dereq_('../../constants/numerical').BADNUM;
var colorscaleCalc = _dereq_('../../components/colorscale/calc');
var _ = _dereq_('../../lib')._;
module.exports = function calc(gd, trace) {
var len = trace._length;
var calcTrace = new Array(len);
var z = trace.z;
var hasZ = isArrayOrTypedArray(z) && z.length;
for(var i = 0; i < len; i++) {
var cdi = calcTrace[i] = {};
var lon = trace.lon[i];
var lat = trace.lat[i];
cdi.lonlat = isNumeric(lon) && isNumeric(lat) ?
[+lon, +lat] :
[BADNUM, BADNUM];
if(hasZ) {
var zi = z[i];
cdi.z = isNumeric(zi) ? zi : BADNUM;
}
}
colorscaleCalc(gd, trace, {
vals: hasZ ? z : [0, 1],
containerStr: '',
cLetter: 'z'
});
if(len) {
calcTrace[0].t = {
labels: {
lat: _(gd, 'lat:') + ' ',
lon: _(gd, 'lon:') + ' '
}
};
}
return calcTrace;
};
},{"../../components/colorscale/calc":374,"../../constants/numerical":479,"../../lib":503,"fast-isnumeric":190}],764:[function(_dereq_,module,exports){
'use strict';
var isNumeric = _dereq_('fast-isnumeric');
var Lib = _dereq_('../../lib');
var Color = _dereq_('../../components/color');
var Colorscale = _dereq_('../../components/colorscale');
var BADNUM = _dereq_('../../constants/numerical').BADNUM;
var makeBlank = _dereq_('../../lib/geojson_utils').makeBlank;
module.exports = function convert(calcTrace) {
var trace = calcTrace[0].trace;
var isVisible = (trace.visible === true && trace._length !== 0);
var heatmap = {
layout: {visibility: 'none'},
paint: {}
};
var opts = trace._opts = {
heatmap: heatmap,
geojson: makeBlank()
};
// early return if not visible or placeholder
if(!isVisible) return opts;
var features = [];
var i;
var z = trace.z;
var radius = trace.radius;
var hasZ = Lib.isArrayOrTypedArray(z) && z.length;
var hasArrayRadius = Lib.isArrayOrTypedArray(radius);
for(i = 0; i < calcTrace.length; i++) {
var cdi = calcTrace[i];
var lonlat = cdi.lonlat;
if(lonlat[0] !== BADNUM) {
var props = {};
if(hasZ) {
var zi = cdi.z;
props.z = zi !== BADNUM ? zi : 0;
}
if(hasArrayRadius) {
props.r = (isNumeric(radius[i]) && radius[i] > 0) ? +radius[i] : 0;
}
features.push({
type: 'Feature',
geometry: {type: 'Point', coordinates: lonlat},
properties: props
});
}
}
var cOpts = Colorscale.extractOpts(trace);
var scl = cOpts.reversescale ?
Colorscale.flipScale(cOpts.colorscale) :
cOpts.colorscale;
// Add alpha channel to first colorscale step.
// If not, we would essentially color the entire map.
// See https://docs.mapbox.com/mapbox-gl-js/example/heatmap-layer/
var scl01 = scl[0][1];
var color0 = Color.opacity(scl01) < 1 ? scl01 : Color.addOpacity(scl01, 0);
var heatmapColor = [
'interpolate', ['linear'],
['heatmap-density'],
0, color0
];
for(i = 1; i < scl.length; i++) {
heatmapColor.push(scl[i][0], scl[i][1]);
}
// Those "weights" have to be in [0, 1], we can do this either:
// - as here using a mapbox-gl expression
// - or, scale the 'z' property in the feature loop
var zExp = [
'interpolate', ['linear'],
['get', 'z'],
cOpts.min, 0,
cOpts.max, 1
];
Lib.extendFlat(opts.heatmap.paint, {
'heatmap-weight': hasZ ? zExp : 1 / (cOpts.max - cOpts.min),
'heatmap-color': heatmapColor,
'heatmap-radius': hasArrayRadius ?
{type: 'identity', property: 'r'} :
trace.radius,
'heatmap-opacity': trace.opacity
});
opts.geojson = {type: 'FeatureCollection', features: features};
opts.heatmap.layout.visibility = 'visible';
return opts;
};
},{"../../components/color":366,"../../components/colorscale":378,"../../constants/numerical":479,"../../lib":503,"../../lib/geojson_utils":497,"fast-isnumeric":190}],765:[function(_dereq_,module,exports){
'use strict';
var Lib = _dereq_('../../lib');
var colorscaleDefaults = _dereq_('../../components/colorscale/defaults');
var attributes = _dereq_('./attributes');
module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout) {
function coerce(attr, dflt) {
return Lib.coerce(traceIn, traceOut, attributes, attr, dflt);
}
var lon = coerce('lon') || [];
var lat = coerce('lat') || [];
var len = Math.min(lon.length, lat.length);
if(!len) {
traceOut.visible = false;
return;
}
traceOut._length = len;
coerce('z');
coerce('radius');
coerce('below');
coerce('text');
coerce('hovertext');
coerce('hovertemplate');
colorscaleDefaults(traceIn, traceOut, layout, coerce, {prefix: '', cLetter: 'z'});
};
},{"../../components/colorscale/defaults":376,"../../lib":503,"./attributes":762}],766:[function(_dereq_,module,exports){
'use strict';
module.exports = function eventData(out, pt) {
out.lon = pt.lon;
out.lat = pt.lat;
out.z = pt.z;
return out;
};
},{}],767:[function(_dereq_,module,exports){
'use strict';
var Axes = _dereq_('../../plots/cartesian/axes');
var scatterMapboxHoverPoints = _dereq_('../scattermapbox/hover').hoverPoints;
var getExtraText = _dereq_('../scattermapbox/hover').getExtraText;
module.exports = function hoverPoints(pointData, xval, yval) {
var pts = scatterMapboxHoverPoints(pointData, xval, yval);
if(!pts) return;
var newPointData = pts[0];
var cd = newPointData.cd;
var trace = cd[0].trace;
var di = cd[newPointData.index];
// let Fx.hover pick the color
delete newPointData.color;
if('z' in di) {
var ax = newPointData.subplot.mockAxis;
newPointData.z = di.z;
newPointData.zLabel = Axes.tickText(ax, ax.c2l(di.z), 'hover').text;
}
newPointData.extraText = getExtraText(trace, di, cd[0].t.labels);
return [newPointData];
};
},{"../../plots/cartesian/axes":554,"../scattermapbox/hover":995}],768:[function(_dereq_,module,exports){
'use strict';
module.exports = {
attributes: _dereq_('./attributes'),
supplyDefaults: _dereq_('./defaults'),
colorbar: _dereq_('../heatmap/colorbar'),
formatLabels: _dereq_('../scattermapbox/format_labels'),
calc: _dereq_('./calc'),
plot: _dereq_('./plot'),
hoverPoints: _dereq_('./hover'),
eventData: _dereq_('./event_data'),
getBelow: function(trace, subplot) {
var mapLayers = subplot.getMapLayers();
// find first layer with `type: 'symbol'`,
// that is not a plotly layer
for(var i = 0; i < mapLayers.length; i++) {
var layer = mapLayers[i];
var layerId = layer.id;
if(layer.type === 'symbol' &&
typeof layerId === 'string' && layerId.indexOf('plotly-') === -1
) {
return layerId;
}
}
},
moduleType: 'trace',
name: 'densitymapbox',
basePlotModule: _dereq_('../../plots/mapbox'),
categories: ['mapbox', 'gl', 'showLegend'],
meta: {
hr_name: 'density_mapbox',
}
};
},{"../../plots/mapbox":613,"../heatmap/colorbar":795,"../scattermapbox/format_labels":994,"./attributes":762,"./calc":763,"./defaults":765,"./event_data":766,"./hover":767,"./plot":769}],769:[function(_dereq_,module,exports){
'use strict';
var convert = _dereq_('./convert');
var LAYER_PREFIX = _dereq_('../../plots/mapbox/constants').traceLayerPrefix;
function DensityMapbox(subplot, uid) {
this.type = 'densitymapbox';
this.subplot = subplot;
this.uid = uid;
this.sourceId = 'source-' + uid;
this.layerList = [
['heatmap', LAYER_PREFIX + uid + '-heatmap']
];
// previous 'below' value,
// need this to update it properly
this.below = null;
}
var proto = DensityMapbox.prototype;
proto.update = function(calcTrace) {
var subplot = this.subplot;
var layerList = this.layerList;
var optsAll = convert(calcTrace);
var below = subplot.belowLookup['trace-' + this.uid];
subplot.map
.getSource(this.sourceId)
.setData(optsAll.geojson);
if(below !== this.below) {
this._removeLayers();
this._addLayers(optsAll, below);
this.below = below;
}
for(var i = 0; i < layerList.length; i++) {
var item = layerList[i];
var k = item[0];
var id = item[1];
var opts = optsAll[k];
subplot.setOptions(id, 'setLayoutProperty', opts.layout);
if(opts.layout.visibility === 'visible') {
subplot.setOptions(id, 'setPaintProperty', opts.paint);
}
}
};
proto._addLayers = function(optsAll, below) {
var subplot = this.subplot;
var layerList = this.layerList;
var sourceId = this.sourceId;
for(var i = 0; i < layerList.length; i++) {
var item = layerList[i];
var k = item[0];
var opts = optsAll[k];
subplot.addLayer({
type: k,
id: item[1],
source: sourceId,
layout: opts.layout,
paint: opts.paint
}, below);
}
};
proto._removeLayers = function() {
var map = this.subplot.map;
var layerList = this.layerList;
for(var i = layerList.length - 1; i >= 0; i--) {
map.removeLayer(layerList[i][1]);
}
};
proto.dispose = function() {
var map = this.subplot.map;
this._removeLayers();
map.removeSource(this.sourceId);
};
module.exports = function createDensityMapbox(subplot, calcTrace) {
var trace = calcTrace[0].trace;
var densityMapbox = new DensityMapbox(subplot, trace.uid);
var sourceId = densityMapbox.sourceId;
var optsAll = convert(calcTrace);
var below = densityMapbox.below = subplot.belowLookup['trace-' + trace.uid];
subplot.map.addSource(sourceId, {
type: 'geojson',
data: optsAll.geojson
});
densityMapbox._addLayers(optsAll, below);
return densityMapbox;
};
},{"../../plots/mapbox/constants":611,"./convert":764}],770:[function(_dereq_,module,exports){
'use strict';
var Lib = _dereq_('../../lib');
// arrayOk attributes, merge them into calcdata array
module.exports = function arraysToCalcdata(cd, trace) {
for(var i = 0; i < cd.length; i++) cd[i].i = i;
Lib.mergeArray(trace.text, cd, 'tx');
Lib.mergeArray(trace.hovertext, cd, 'htx');
var marker = trace.marker;
if(marker) {
Lib.mergeArray(marker.opacity, cd, 'mo');
Lib.mergeArray(marker.color, cd, 'mc');
var markerLine = marker.line;
if(markerLine) {
Lib.mergeArray(markerLine.color, cd, 'mlc');
Lib.mergeArrayCastPositive(markerLine.width, cd, 'mlw');
}
}
};
},{"../../lib":503}],771:[function(_dereq_,module,exports){
'use strict';
var barAttrs = _dereq_('../bar/attributes');
var lineAttrs = _dereq_('../scatter/attributes').line;
var baseAttrs = _dereq_('../../plots/attributes');
var axisHoverFormat = _dereq_('../../plots/cartesian/axis_format_attributes').axisHoverFormat;
var hovertemplateAttrs = _dereq_('../../plots/template_attributes').hovertemplateAttrs;
var texttemplateAttrs = _dereq_('../../plots/template_attributes').texttemplateAttrs;
var constants = _dereq_('./constants');
var extendFlat = _dereq_('../../lib/extend').extendFlat;
var Color = _dereq_('../../components/color');
module.exports = {
x: barAttrs.x,
x0: barAttrs.x0,
dx: barAttrs.dx,
y: barAttrs.y,
y0: barAttrs.y0,
dy: barAttrs.dy,
xperiod: barAttrs.xperiod,
yperiod: barAttrs.yperiod,
xperiod0: barAttrs.xperiod0,
yperiod0: barAttrs.yperiod0,
xperiodalignment: barAttrs.xperiodalignment,
yperiodalignment: barAttrs.yperiodalignment,
xhoverformat: axisHoverFormat('x'),
yhoverformat: axisHoverFormat('y'),
hovertext: barAttrs.hovertext,
hovertemplate: hovertemplateAttrs({}, {
keys: constants.eventDataKeys
}),
hoverinfo: extendFlat({}, baseAttrs.hoverinfo, {
flags: ['name', 'x', 'y', 'text', 'percent initial', 'percent previous', 'percent total']
}),
textinfo: {
valType: 'flaglist',
flags: ['label', 'text', 'percent initial', 'percent previous', 'percent total', 'value'],
extras: ['none'],
editType: 'plot',
arrayOk: false,
},
// TODO: incorporate `label` and `value` in the eventData
texttemplate: texttemplateAttrs({editType: 'plot'}, {
keys: constants.eventDataKeys.concat(['label', 'value'])
}),
text: barAttrs.text,
textposition: barAttrs.textposition,
insidetextanchor: extendFlat({}, barAttrs.insidetextanchor, {dflt: 'middle'}),
textangle: extendFlat({}, barAttrs.textangle, {dflt: 0}),
textfont: barAttrs.textfont,
insidetextfont: barAttrs.insidetextfont,
outsidetextfont: barAttrs.outsidetextfont,
constraintext: barAttrs.constraintext,
cliponaxis: barAttrs.cliponaxis,
orientation: extendFlat({}, barAttrs.orientation, {
}),
offset: extendFlat({}, barAttrs.offset, {arrayOk: false}),
width: extendFlat({}, barAttrs.width, {arrayOk: false}),
marker: funnelMarker(),
connector: {
fillcolor: {
valType: 'color',
editType: 'style',
},
line: {
color: extendFlat({}, lineAttrs.color, {dflt: Color.defaultLine}),
width: extendFlat({}, lineAttrs.width, {
dflt: 0,
editType: 'plot',
}),
dash: lineAttrs.dash,
editType: 'style'
},
visible: {
valType: 'boolean',
dflt: true,
editType: 'plot',
},
editType: 'plot'
},
offsetgroup: barAttrs.offsetgroup,
alignmentgroup: barAttrs.alignmentgroup
};
function funnelMarker() {
var marker = extendFlat({}, barAttrs.marker);
delete marker.pattern;
return marker;
}
},{"../../components/color":366,"../../lib/extend":493,"../../plots/attributes":550,"../../plots/cartesian/axis_format_attributes":557,"../../plots/template_attributes":633,"../bar/attributes":648,"../scatter/attributes":925,"./constants":773}],772:[function(_dereq_,module,exports){
'use strict';
var Axes = _dereq_('../../plots/cartesian/axes');
var alignPeriod = _dereq_('../../plots/cartesian/align_period');
var arraysToCalcdata = _dereq_('./arrays_to_calcdata');
var calcSelection = _dereq_('../scatter/calc_selection');
var BADNUM = _dereq_('../../constants/numerical').BADNUM;
module.exports = function calc(gd, trace) {
var xa = Axes.getFromId(gd, trace.xaxis || 'x');
var ya = Axes.getFromId(gd, trace.yaxis || 'y');
var size, pos, origPos, pObj, hasPeriod, pLetter, i, cdi;
if(trace.orientation === 'h') {
size = xa.makeCalcdata(trace, 'x');
origPos = ya.makeCalcdata(trace, 'y');
pObj = alignPeriod(trace, ya, 'y', origPos);
hasPeriod = !!trace.yperiodalignment;
pLetter = 'y';
} else {
size = ya.makeCalcdata(trace, 'y');
origPos = xa.makeCalcdata(trace, 'x');
pObj = alignPeriod(trace, xa, 'x', origPos);
hasPeriod = !!trace.xperiodalignment;
pLetter = 'x';
}
pos = pObj.vals;
// create the "calculated data" to plot
var serieslen = Math.min(pos.length, size.length);
var cd = new Array(serieslen);
// Unlike other bar-like traces funnels do not support base attribute.
// bases for funnels are computed internally in a way that
// the mid-point of each bar are located on the axis line.
trace._base = [];
// set position and size
for(i = 0; i < serieslen; i++) {
// treat negative values as bad numbers
if(size[i] < 0) size[i] = BADNUM;
var connectToNext = false;
if(size[i] !== BADNUM) {
if(i + 1 < serieslen && size[i + 1] !== BADNUM) {
connectToNext = true;
}
}
cdi = cd[i] = {
p: pos[i],
s: size[i],
cNext: connectToNext
};
trace._base[i] = -0.5 * cdi.s;
if(hasPeriod) {
cd[i].orig_p = origPos[i]; // used by hover
cd[i][pLetter + 'End'] = pObj.ends[i];
cd[i][pLetter + 'Start'] = pObj.starts[i];
}
if(trace.ids) {
cdi.id = String(trace.ids[i]);
}
// calculate total values
if(i === 0) cd[0].vTotal = 0;
cd[0].vTotal += fixNum(cdi.s);
// ratio from initial value
cdi.begR = fixNum(cdi.s) / fixNum(cd[0].s);
}
var prevGoodNum;
for(i = 0; i < serieslen; i++) {
cdi = cd[i];
if(cdi.s === BADNUM) continue;
// ratio of total value
cdi.sumR = cdi.s / cd[0].vTotal;
// ratio of previous (good) value
cdi.difR = (prevGoodNum !== undefined) ? cdi.s / prevGoodNum : 1;
prevGoodNum = cdi.s;
}
arraysToCalcdata(cd, trace);
calcSelection(cd, trace);
return cd;
};
function fixNum(a) {
return (a === BADNUM) ? 0 : a;
}
},{"../../constants/numerical":479,"../../plots/cartesian/align_period":551,"../../plots/cartesian/axes":554,"../scatter/calc_selection":927,"./arrays_to_calcdata":770}],773:[function(_dereq_,module,exports){
'use strict';
module.exports = {
eventDataKeys: [
'percentInitial',
'percentPrevious',
'percentTotal'
]
};
},{}],774:[function(_dereq_,module,exports){
'use strict';
var setGroupPositions = _dereq_('../bar/cross_trace_calc').setGroupPositions;
module.exports = function crossTraceCalc(gd, plotinfo) {
var fullLayout = gd._fullLayout;
var fullData = gd._fullData;
var calcdata = gd.calcdata;
var xa = plotinfo.xaxis;
var ya = plotinfo.yaxis;
var funnels = [];
var funnelsVert = [];
var funnelsHorz = [];
var cd, i;
for(i = 0; i < fullData.length; i++) {
var fullTrace = fullData[i];
var isHorizontal = (fullTrace.orientation === 'h');
if(
fullTrace.visible === true &&
fullTrace.xaxis === xa._id &&
fullTrace.yaxis === ya._id &&
fullTrace.type === 'funnel'
) {
cd = calcdata[i];
if(isHorizontal) {
funnelsHorz.push(cd);
} else {
funnelsVert.push(cd);
}
funnels.push(cd);
}
}
var opts = {
mode: fullLayout.funnelmode,
norm: fullLayout.funnelnorm,
gap: fullLayout.funnelgap,
groupgap: fullLayout.funnelgroupgap
};
setGroupPositions(gd, xa, ya, funnelsVert, opts);
setGroupPositions(gd, ya, xa, funnelsHorz, opts);
for(i = 0; i < funnels.length; i++) {
cd = funnels[i];
for(var j = 0; j < cd.length; j++) {
if(j + 1 < cd.length) {
cd[j].nextP0 = cd[j + 1].p0;
cd[j].nextS0 = cd[j + 1].s0;
cd[j].nextP1 = cd[j + 1].p1;
cd[j].nextS1 = cd[j + 1].s1;
}
}
}
};
},{"../bar/cross_trace_calc":651}],775:[function(_dereq_,module,exports){
'use strict';
var Lib = _dereq_('../../lib');
var handleGroupingDefaults = _dereq_('../bar/defaults').handleGroupingDefaults;
var handleText = _dereq_('../bar/defaults').handleText;
var handleXYDefaults = _dereq_('../scatter/xy_defaults');
var handlePeriodDefaults = _dereq_('../scatter/period_defaults');
var attributes = _dereq_('./attributes');
var Color = _dereq_('../../components/color');
function supplyDefaults(traceIn, traceOut, defaultColor, layout) {
function coerce(attr, dflt) {
return Lib.coerce(traceIn, traceOut, attributes, attr, dflt);
}
var len = handleXYDefaults(traceIn, traceOut, layout, coerce);
if(!len) {
traceOut.visible = false;
return;
}
handlePeriodDefaults(traceIn, traceOut, layout, coerce);
coerce('xhoverformat');
coerce('yhoverformat');
coerce('orientation', (traceOut.y && !traceOut.x) ? 'v' : 'h');
coerce('offset');
coerce('width');
var text = coerce('text');
coerce('hovertext');
coerce('hovertemplate');
var textposition = coerce('textposition');
handleText(traceIn, traceOut, layout, coerce, textposition, {
moduleHasSelected: false,
moduleHasUnselected: false,
moduleHasConstrain: true,
moduleHasCliponaxis: true,
moduleHasTextangle: true,
moduleHasInsideanchor: true
});
if(traceOut.textposition !== 'none' && !traceOut.texttemplate) {
coerce('textinfo', Array.isArray(text) ? 'text+value' : 'value');
}
var markerColor = coerce('marker.color', defaultColor);
coerce('marker.line.color', Color.defaultLine);
coerce('marker.line.width');
var connectorVisible = coerce('connector.visible');
if(connectorVisible) {
coerce('connector.fillcolor', defaultFillColor(markerColor));
var connectorLineWidth = coerce('connector.line.width');
if(connectorLineWidth) {
coerce('connector.line.color');
coerce('connector.line.dash');
}
}
}
function defaultFillColor(markerColor) {
var cBase = Lib.isArrayOrTypedArray(markerColor) ? '#000' : markerColor;
return Color.addOpacity(cBase, 0.5 * Color.opacity(cBase));
}
function crossTraceDefaults(fullData, fullLayout) {
var traceIn, traceOut;
function coerce(attr) {
return Lib.coerce(traceOut._input, traceOut, attributes, attr);
}
if(fullLayout.funnelmode === 'group') {
for(var i = 0; i < fullData.length; i++) {
traceOut = fullData[i];
traceIn = traceOut._input;
handleGroupingDefaults(traceIn, traceOut, fullLayout, coerce);
}
}
}
module.exports = {
supplyDefaults: supplyDefaults,
crossTraceDefaults: crossTraceDefaults
};
},{"../../components/color":366,"../../lib":503,"../bar/defaults":652,"../scatter/period_defaults":945,"../scatter/xy_defaults":952,"./attributes":771}],776:[function(_dereq_,module,exports){
'use strict';
module.exports = function eventData(out, pt /* , trace, cd, pointNumber */) {
// standard cartesian event data
out.x = 'xVal' in pt ? pt.xVal : pt.x;
out.y = 'yVal' in pt ? pt.yVal : pt.y;
// for funnel
if('percentInitial' in pt) out.percentInitial = pt.percentInitial;
if('percentPrevious' in pt) out.percentPrevious = pt.percentPrevious;
if('percentTotal' in pt) out.percentTotal = pt.percentTotal;
if(pt.xa) out.xaxis = pt.xa;
if(pt.ya) out.yaxis = pt.ya;
return out;
};
},{}],777:[function(_dereq_,module,exports){
'use strict';
var opacity = _dereq_('../../components/color').opacity;
var hoverOnBars = _dereq_('../bar/hover').hoverOnBars;
var formatPercent = _dereq_('../../lib').formatPercent;
module.exports = function hoverPoints(pointData, xval, yval, hovermode, opts) {
var point = hoverOnBars(pointData, xval, yval, hovermode, opts);
if(!point) return;
var cd = point.cd;
var trace = cd[0].trace;
var isHorizontal = (trace.orientation === 'h');
// the closest data point
var index = point.index;
var di = cd[index];
var sizeLetter = isHorizontal ? 'x' : 'y';
point[sizeLetter + 'LabelVal'] = di.s;
point.percentInitial = di.begR;
point.percentInitialLabel = formatPercent(di.begR, 1);
point.percentPrevious = di.difR;
point.percentPreviousLabel = formatPercent(di.difR, 1);
point.percentTotal = di.sumR;
point.percentTotalLabel = formatPercent(di.sumR, 1);
var hoverinfo = di.hi || trace.hoverinfo;
var text = [];
if(hoverinfo && hoverinfo !== 'none' && hoverinfo !== 'skip') {
var isAll = (hoverinfo === 'all');
var parts = hoverinfo.split('+');
var hasFlag = function(flag) { return isAll || parts.indexOf(flag) !== -1; };
if(hasFlag('percent initial')) {
text.push(point.percentInitialLabel + ' of initial');
}
if(hasFlag('percent previous')) {
text.push(point.percentPreviousLabel + ' of previous');
}
if(hasFlag('percent total')) {
text.push(point.percentTotalLabel + ' of total');
}
}
point.extraText = text.join('
');
point.color = getTraceColor(trace, di);
return [point];
};
function getTraceColor(trace, di) {
var cont = trace.marker;
var mc = di.mc || cont.color;
var mlc = di.mlc || cont.line.color;
var mlw = di.mlw || cont.line.width;
if(opacity(mc)) return mc;
else if(opacity(mlc) && mlw) return mlc;
}
},{"../../components/color":366,"../../lib":503,"../bar/hover":655}],778:[function(_dereq_,module,exports){
'use strict';
module.exports = {
attributes: _dereq_('./attributes'),
layoutAttributes: _dereq_('./layout_attributes'),
supplyDefaults: _dereq_('./defaults').supplyDefaults,
crossTraceDefaults: _dereq_('./defaults').crossTraceDefaults,
supplyLayoutDefaults: _dereq_('./layout_defaults'),
calc: _dereq_('./calc'),
crossTraceCalc: _dereq_('./cross_trace_calc'),
plot: _dereq_('./plot'),
style: _dereq_('./style').style,
hoverPoints: _dereq_('./hover'),
eventData: _dereq_('./event_data'),
selectPoints: _dereq_('../bar/select'),
moduleType: 'trace',
name: 'funnel',
basePlotModule: _dereq_('../../plots/cartesian'),
categories: ['bar-like', 'cartesian', 'svg', 'oriented', 'showLegend', 'zoomScale'],
meta: {
}
};
},{"../../plots/cartesian":568,"../bar/select":660,"./attributes":771,"./calc":772,"./cross_trace_calc":774,"./defaults":775,"./event_data":776,"./hover":777,"./layout_attributes":779,"./layout_defaults":780,"./plot":781,"./style":782}],779:[function(_dereq_,module,exports){
'use strict';
module.exports = {
funnelmode: {
valType: 'enumerated',
values: ['stack', 'group', 'overlay'],
dflt: 'stack',
editType: 'calc',
},
funnelgap: {
valType: 'number',
min: 0,
max: 1,
editType: 'calc',
},
funnelgroupgap: {
valType: 'number',
min: 0,
max: 1,
dflt: 0,
editType: 'calc',
}
};
},{}],780:[function(_dereq_,module,exports){
'use strict';
var Lib = _dereq_('../../lib');
var layoutAttributes = _dereq_('./layout_attributes');
module.exports = function(layoutIn, layoutOut, fullData) {
var hasTraceType = false;
function coerce(attr, dflt) {
return Lib.coerce(layoutIn, layoutOut, layoutAttributes, attr, dflt);
}
for(var i = 0; i < fullData.length; i++) {
var trace = fullData[i];
if(trace.visible && trace.type === 'funnel') {
hasTraceType = true;
break;
}
}
if(hasTraceType) {
coerce('funnelmode');
coerce('funnelgap', 0.2);
coerce('funnelgroupgap');
}
};
},{"../../lib":503,"./layout_attributes":779}],781:[function(_dereq_,module,exports){
'use strict';
var d3 = _dereq_('@plotly/d3');
var Lib = _dereq_('../../lib');
var Drawing = _dereq_('../../components/drawing');
var BADNUM = _dereq_('../../constants/numerical').BADNUM;
var barPlot = _dereq_('../bar/plot');
var clearMinTextSize = _dereq_('../bar/uniform_text').clearMinTextSize;
module.exports = function plot(gd, plotinfo, cdModule, traceLayer) {
var fullLayout = gd._fullLayout;
clearMinTextSize('funnel', fullLayout);
plotConnectorRegions(gd, plotinfo, cdModule, traceLayer);
plotConnectorLines(gd, plotinfo, cdModule, traceLayer);
barPlot.plot(gd, plotinfo, cdModule, traceLayer, {
mode: fullLayout.funnelmode,
norm: fullLayout.funnelmode,
gap: fullLayout.funnelgap,
groupgap: fullLayout.funnelgroupgap
});
};
function plotConnectorRegions(gd, plotinfo, cdModule, traceLayer) {
var xa = plotinfo.xaxis;
var ya = plotinfo.yaxis;
Lib.makeTraceGroups(traceLayer, cdModule, 'trace bars').each(function(cd) {
var plotGroup = d3.select(this);
var trace = cd[0].trace;
var group = Lib.ensureSingle(plotGroup, 'g', 'regions');
if(!trace.connector || !trace.connector.visible) {
group.remove();
return;
}
var isHorizontal = (trace.orientation === 'h');
var connectors = group.selectAll('g.region').data(Lib.identity);
connectors.enter().append('g')
.classed('region', true);
connectors.exit().remove();
var len = connectors.size();
connectors.each(function(di, i) {
// don't draw lines between nulls
if(i !== len - 1 && !di.cNext) return;
var xy = getXY(di, xa, ya, isHorizontal);
var x = xy[0];
var y = xy[1];
var shape = '';
if(
x[0] !== BADNUM && y[0] !== BADNUM &&
x[1] !== BADNUM && y[1] !== BADNUM &&
x[2] !== BADNUM && y[2] !== BADNUM &&
x[3] !== BADNUM && y[3] !== BADNUM
) {
if(isHorizontal) {
shape += 'M' + x[0] + ',' + y[1] + 'L' + x[2] + ',' + y[2] + 'H' + x[3] + 'L' + x[1] + ',' + y[1] + 'Z';
} else {
shape += 'M' + x[1] + ',' + y[1] + 'L' + x[2] + ',' + y[3] + 'V' + y[2] + 'L' + x[1] + ',' + y[0] + 'Z';
}
}
if(shape === '') shape = 'M0,0Z';
Lib.ensureSingle(d3.select(this), 'path')
.attr('d', shape)
.call(Drawing.setClipUrl, plotinfo.layerClipId, gd);
});
});
}
function plotConnectorLines(gd, plotinfo, cdModule, traceLayer) {
var xa = plotinfo.xaxis;
var ya = plotinfo.yaxis;
Lib.makeTraceGroups(traceLayer, cdModule, 'trace bars').each(function(cd) {
var plotGroup = d3.select(this);
var trace = cd[0].trace;
var group = Lib.ensureSingle(plotGroup, 'g', 'lines');
if(!trace.connector || !trace.connector.visible || !trace.connector.line.width) {
group.remove();
return;
}
var isHorizontal = (trace.orientation === 'h');
var connectors = group.selectAll('g.line').data(Lib.identity);
connectors.enter().append('g')
.classed('line', true);
connectors.exit().remove();
var len = connectors.size();
connectors.each(function(di, i) {
// don't draw lines between nulls
if(i !== len - 1 && !di.cNext) return;
var xy = getXY(di, xa, ya, isHorizontal);
var x = xy[0];
var y = xy[1];
var shape = '';
if(x[3] !== undefined && y[3] !== undefined) {
if(isHorizontal) {
shape += 'M' + x[0] + ',' + y[1] + 'L' + x[2] + ',' + y[2];
shape += 'M' + x[1] + ',' + y[1] + 'L' + x[3] + ',' + y[2];
} else {
shape += 'M' + x[1] + ',' + y[1] + 'L' + x[2] + ',' + y[3];
shape += 'M' + x[1] + ',' + y[0] + 'L' + x[2] + ',' + y[2];
}
}
if(shape === '') shape = 'M0,0Z';
Lib.ensureSingle(d3.select(this), 'path')
.attr('d', shape)
.call(Drawing.setClipUrl, plotinfo.layerClipId, gd);
});
});
}
function getXY(di, xa, ya, isHorizontal) {
var s = [];
var p = [];
var sAxis = isHorizontal ? xa : ya;
var pAxis = isHorizontal ? ya : xa;
s[0] = sAxis.c2p(di.s0, true);
p[0] = pAxis.c2p(di.p0, true);
s[1] = sAxis.c2p(di.s1, true);
p[1] = pAxis.c2p(di.p1, true);
s[2] = sAxis.c2p(di.nextS0, true);
p[2] = pAxis.c2p(di.nextP0, true);
s[3] = sAxis.c2p(di.nextS1, true);
p[3] = pAxis.c2p(di.nextP1, true);
return isHorizontal ? [s, p] : [p, s];
}
},{"../../components/drawing":388,"../../constants/numerical":479,"../../lib":503,"../bar/plot":659,"../bar/uniform_text":664,"@plotly/d3":58}],782:[function(_dereq_,module,exports){
'use strict';
var d3 = _dereq_('@plotly/d3');
var Drawing = _dereq_('../../components/drawing');
var Color = _dereq_('../../components/color');
var DESELECTDIM = _dereq_('../../constants/interactions').DESELECTDIM;
var barStyle = _dereq_('../bar/style');
var resizeText = _dereq_('../bar/uniform_text').resizeText;
var styleTextPoints = barStyle.styleTextPoints;
function style(gd, cd, sel) {
var s = sel ? sel : d3.select(gd).selectAll('g.funnellayer').selectAll('g.trace');
resizeText(gd, s, 'funnel');
s.style('opacity', function(d) { return d[0].trace.opacity; });
s.each(function(d) {
var gTrace = d3.select(this);
var trace = d[0].trace;
gTrace.selectAll('.point > path').each(function(di) {
if(!di.isBlank) {
var cont = trace.marker;
d3.select(this)
.call(Color.fill, di.mc || cont.color)
.call(Color.stroke, di.mlc || cont.line.color)
.call(Drawing.dashLine, cont.line.dash, di.mlw || cont.line.width)
.style('opacity', trace.selectedpoints && !di.selected ? DESELECTDIM : 1);
}
});
styleTextPoints(gTrace, trace, gd);
gTrace.selectAll('.regions').each(function() {
d3.select(this).selectAll('path').style('stroke-width', 0).call(Color.fill, trace.connector.fillcolor);
});
gTrace.selectAll('.lines').each(function() {
var cont = trace.connector.line;
Drawing.lineGroupStyle(
d3.select(this).selectAll('path'),
cont.width,
cont.color,
cont.dash
);
});
});
}
module.exports = {
style: style
};
},{"../../components/color":366,"../../components/drawing":388,"../../constants/interactions":478,"../bar/style":662,"../bar/uniform_text":664,"@plotly/d3":58}],783:[function(_dereq_,module,exports){
'use strict';
var pieAttrs = _dereq_('../pie/attributes');
var baseAttrs = _dereq_('../../plots/attributes');
var domainAttrs = _dereq_('../../plots/domain').attributes;
var hovertemplateAttrs = _dereq_('../../plots/template_attributes').hovertemplateAttrs;
var texttemplateAttrs = _dereq_('../../plots/template_attributes').texttemplateAttrs;
var extendFlat = _dereq_('../../lib/extend').extendFlat;
module.exports = {
labels: pieAttrs.labels,
// equivalent of x0 and dx, if label is missing
label0: pieAttrs.label0,
dlabel: pieAttrs.dlabel,
values: pieAttrs.values,
marker: {
colors: pieAttrs.marker.colors,
line: {
color: extendFlat({}, pieAttrs.marker.line.color, {
dflt: null,
}),
width: extendFlat({}, pieAttrs.marker.line.width, {dflt: 1}),
editType: 'calc'
},
editType: 'calc'
},
text: pieAttrs.text,
hovertext: pieAttrs.hovertext,
scalegroup: extendFlat({}, pieAttrs.scalegroup, {
}),
textinfo: extendFlat({}, pieAttrs.textinfo, {
flags: ['label', 'text', 'value', 'percent']
}),
texttemplate: texttemplateAttrs({editType: 'plot'}, {
keys: ['label', 'color', 'value', 'text', 'percent']
}),
hoverinfo: extendFlat({}, baseAttrs.hoverinfo, {
flags: ['label', 'text', 'value', 'percent', 'name']
}),
hovertemplate: hovertemplateAttrs({}, {
keys: ['label', 'color', 'value', 'text', 'percent']
}),
textposition: extendFlat({}, pieAttrs.textposition, {
values: ['inside', 'none'],
dflt: 'inside'
}),
textfont: pieAttrs.textfont,
insidetextfont: pieAttrs.insidetextfont,
title: {
text: pieAttrs.title.text,
font: pieAttrs.title.font,
position: extendFlat({}, pieAttrs.title.position, {
values: ['top left', 'top center', 'top right'],
dflt: 'top center'
}),
editType: 'plot'
},
domain: domainAttrs({name: 'funnelarea', trace: true, editType: 'calc'}),
aspectratio: {
valType: 'number',
min: 0,
dflt: 1,
editType: 'plot',
},
baseratio: {
valType: 'number',
min: 0,
max: 1,
dflt: 0.333,
editType: 'plot',
}
};
},{"../../lib/extend":493,"../../plots/attributes":550,"../../plots/domain":584,"../../plots/template_attributes":633,"../pie/attributes":899}],784:[function(_dereq_,module,exports){
'use strict';
var plots = _dereq_('../../plots/plots');
exports.name = 'funnelarea';
exports.plot = function(gd, traces, transitionOpts, makeOnCompleteCallback) {
plots.plotBasePlot(exports.name, gd, traces, transitionOpts, makeOnCompleteCallback);
};
exports.clean = function(newFullData, newFullLayout, oldFullData, oldFullLayout) {
plots.cleanBasePlot(exports.name, newFullData, newFullLayout, oldFullData, oldFullLayout);
};
},{"../../plots/plots":619}],785:[function(_dereq_,module,exports){
'use strict';
var pieCalc = _dereq_('../pie/calc');
function calc(gd, trace) {
return pieCalc.calc(gd, trace);
}
function crossTraceCalc(gd) {
pieCalc.crossTraceCalc(gd, { type: 'funnelarea' });
}
module.exports = {
calc: calc,
crossTraceCalc: crossTraceCalc
};
},{"../pie/calc":901}],786:[function(_dereq_,module,exports){
'use strict';
var Lib = _dereq_('../../lib');
var attributes = _dereq_('./attributes');
var handleDomainDefaults = _dereq_('../../plots/domain').defaults;
var handleText = _dereq_('../bar/defaults').handleText;
var handleLabelsAndValues = _dereq_('../pie/defaults').handleLabelsAndValues;
module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout) {
function coerce(attr, dflt) {
return Lib.coerce(traceIn, traceOut, attributes, attr, dflt);
}
var labels = coerce('labels');
var values = coerce('values');
var res = handleLabelsAndValues(labels, values);
var len = res.len;
traceOut._hasLabels = res.hasLabels;
traceOut._hasValues = res.hasValues;
if(!traceOut._hasLabels &&
traceOut._hasValues
) {
coerce('label0');
coerce('dlabel');
}
if(!len) {
traceOut.visible = false;
return;
}
traceOut._length = len;
var lineWidth = coerce('marker.line.width');
if(lineWidth) coerce('marker.line.color', layout.paper_bgcolor);
coerce('marker.colors');
coerce('scalegroup');
var textData = coerce('text');
var textTemplate = coerce('texttemplate');
var textInfo;
if(!textTemplate) textInfo = coerce('textinfo', Array.isArray(textData) ? 'text+percent' : 'percent');
coerce('hovertext');
coerce('hovertemplate');
if(textTemplate || (textInfo && textInfo !== 'none')) {
var textposition = coerce('textposition');
handleText(traceIn, traceOut, layout, coerce, textposition, {
moduleHasSelected: false,
moduleHasUnselected: false,
moduleHasConstrain: false,
moduleHasCliponaxis: false,
moduleHasTextangle: false,
moduleHasInsideanchor: false
});
}
handleDomainDefaults(traceOut, layout, coerce);
var title = coerce('title.text');
if(title) {
coerce('title.position');
Lib.coerceFont(coerce, 'title.font', layout.font);
}
coerce('aspectratio');
coerce('baseratio');
};
},{"../../lib":503,"../../plots/domain":584,"../bar/defaults":652,"../pie/defaults":902,"./attributes":783}],787:[function(_dereq_,module,exports){
'use strict';
module.exports = {
moduleType: 'trace',
name: 'funnelarea',
basePlotModule: _dereq_('./base_plot'),
categories: ['pie-like', 'funnelarea', 'showLegend'],
attributes: _dereq_('./attributes'),
layoutAttributes: _dereq_('./layout_attributes'),
supplyDefaults: _dereq_('./defaults'),
supplyLayoutDefaults: _dereq_('./layout_defaults'),
calc: _dereq_('./calc').calc,
crossTraceCalc: _dereq_('./calc').crossTraceCalc,
plot: _dereq_('./plot'),
style: _dereq_('./style'),
styleOne: _dereq_('../pie/style_one'),
meta: {
}
};
},{"../pie/style_one":910,"./attributes":783,"./base_plot":784,"./calc":785,"./defaults":786,"./layout_attributes":788,"./layout_defaults":789,"./plot":790,"./style":791}],788:[function(_dereq_,module,exports){
'use strict';
var hiddenlabels = _dereq_('../pie/layout_attributes').hiddenlabels;
module.exports = {
hiddenlabels: hiddenlabels,
funnelareacolorway: {
valType: 'colorlist',
editType: 'calc',
},
extendfunnelareacolors: {
valType: 'boolean',
dflt: true,
editType: 'calc',
}
};
},{"../pie/layout_attributes":906}],789:[function(_dereq_,module,exports){
'use strict';
var Lib = _dereq_('../../lib');
var layoutAttributes = _dereq_('./layout_attributes');
module.exports = function supplyLayoutDefaults(layoutIn, layoutOut) {
function coerce(attr, dflt) {
return Lib.coerce(layoutIn, layoutOut, layoutAttributes, attr, dflt);
}
coerce('hiddenlabels');
coerce('funnelareacolorway', layoutOut.colorway);
coerce('extendfunnelareacolors');
};
},{"../../lib":503,"./layout_attributes":788}],790:[function(_dereq_,module,exports){
'use strict';
var d3 = _dereq_('@plotly/d3');
var Drawing = _dereq_('../../components/drawing');
var Lib = _dereq_('../../lib');
var strScale = Lib.strScale;
var strTranslate = Lib.strTranslate;
var svgTextUtils = _dereq_('../../lib/svg_text_utils');
var barPlot = _dereq_('../bar/plot');
var toMoveInsideBar = barPlot.toMoveInsideBar;
var uniformText = _dereq_('../bar/uniform_text');
var recordMinTextSize = uniformText.recordMinTextSize;
var clearMinTextSize = uniformText.clearMinTextSize;
var pieHelpers = _dereq_('../pie/helpers');
var piePlot = _dereq_('../pie/plot');
var attachFxHandlers = piePlot.attachFxHandlers;
var determineInsideTextFont = piePlot.determineInsideTextFont;
var layoutAreas = piePlot.layoutAreas;
var prerenderTitles = piePlot.prerenderTitles;
var positionTitleOutside = piePlot.positionTitleOutside;
var formatSliceLabel = piePlot.formatSliceLabel;
module.exports = function plot(gd, cdModule) {
var fullLayout = gd._fullLayout;
clearMinTextSize('funnelarea', fullLayout);
prerenderTitles(cdModule, gd);
layoutAreas(cdModule, fullLayout._size);
Lib.makeTraceGroups(fullLayout._funnelarealayer, cdModule, 'trace').each(function(cd) {
var plotGroup = d3.select(this);
var cd0 = cd[0];
var trace = cd0.trace;
setCoords(cd);
plotGroup.each(function() {
var slices = d3.select(this).selectAll('g.slice').data(cd);
slices.enter().append('g')
.classed('slice', true);
slices.exit().remove();
slices.each(function(pt, i) {
if(pt.hidden) {
d3.select(this).selectAll('path,g').remove();
return;
}
// to have consistent event data compared to other traces
pt.pointNumber = pt.i;
pt.curveNumber = trace.index;
var cx = cd0.cx;
var cy = cd0.cy;
var sliceTop = d3.select(this);
var slicePath = sliceTop.selectAll('path.surface').data([pt]);
slicePath.enter().append('path')
.classed('surface', true)
.style({'pointer-events': 'all'});
sliceTop.call(attachFxHandlers, gd, cd);
var shape =
'M' + (cx + pt.TR[0]) + ',' + (cy + pt.TR[1]) +
line(pt.TR, pt.BR) +
line(pt.BR, pt.BL) +
line(pt.BL, pt.TL) +
'Z';
slicePath.attr('d', shape);
// add text
formatSliceLabel(gd, pt, cd0);
var textPosition = pieHelpers.castOption(trace.textposition, pt.pts);
var sliceTextGroup = sliceTop.selectAll('g.slicetext')
.data(pt.text && (textPosition !== 'none') ? [0] : []);
sliceTextGroup.enter().append('g')
.classed('slicetext', true);
sliceTextGroup.exit().remove();
sliceTextGroup.each(function() {
var sliceText = Lib.ensureSingle(d3.select(this), 'text', '', function(s) {
// prohibit tex interpretation until we can handle
// tex and regular text together
s.attr('data-notex', 1);
});
var font = Lib.ensureUniformFontSize(gd, determineInsideTextFont(trace, pt, fullLayout.font));
sliceText.text(pt.text)
.attr({
'class': 'slicetext',
transform: '',
'text-anchor': 'middle'
})
.call(Drawing.font, font)
.call(svgTextUtils.convertToTspans, gd);
// position the text relative to the slice
var textBB = Drawing.bBox(sliceText.node());
var transform;
var x0, x1;
var y0 = Math.min(pt.BL[1], pt.BR[1]) + cy;
var y1 = Math.max(pt.TL[1], pt.TR[1]) + cy;
x0 = Math.max(pt.TL[0], pt.BL[0]) + cx;
x1 = Math.min(pt.TR[0], pt.BR[0]) + cx;
transform = toMoveInsideBar(x0, x1, y0, y1, textBB, {
isHorizontal: true,
constrained: true,
angle: 0,
anchor: 'middle'
});
transform.fontSize = font.size;
recordMinTextSize(trace.type, transform, fullLayout);
cd[i].transform = transform;
sliceText.attr('transform', Lib.getTextTransform(transform));
});
});
// add the title
var titleTextGroup = d3.select(this).selectAll('g.titletext')
.data(trace.title.text ? [0] : []);
titleTextGroup.enter().append('g')
.classed('titletext', true);
titleTextGroup.exit().remove();
titleTextGroup.each(function() {
var titleText = Lib.ensureSingle(d3.select(this), 'text', '', function(s) {
// prohibit tex interpretation as above
s.attr('data-notex', 1);
});
var txt = trace.title.text;
if(trace._meta) {
txt = Lib.templateString(txt, trace._meta);
}
titleText.text(txt)
.attr({
'class': 'titletext',
transform: '',
'text-anchor': 'middle',
})
.call(Drawing.font, trace.title.font)
.call(svgTextUtils.convertToTspans, gd);
var transform = positionTitleOutside(cd0, fullLayout._size);
titleText.attr('transform',
strTranslate(transform.x, transform.y) +
strScale(Math.min(1, transform.scale)) +
strTranslate(transform.tx, transform.ty));
});
});
});
};
function line(a, b) {
var dx = b[0] - a[0];
var dy = b[1] - a[1];
return 'l' + dx + ',' + dy;
}
function getBetween(a, b) {
return [
0.5 * (a[0] + b[0]),
0.5 * (a[1] + b[1])
];
}
function setCoords(cd) {
if(!cd.length) return;
var cd0 = cd[0];
var trace = cd0.trace;
var aspectratio = trace.aspectratio;
var h = trace.baseratio;
if(h > 0.999) h = 0.999; // TODO: may handle this case separately
var h2 = Math.pow(h, 2);
var v1 = cd0.vTotal;
var v0 = v1 * h2 / (1 - h2);
var totalValues = v1;
var sumSteps = v0 / v1;
function calcPos() {
var q = Math.sqrt(sumSteps);
return {
x: q,
y: -q
};
}
function getPoint() {
var pos = calcPos();
return [pos.x, pos.y];
}
var p;
var allPoints = [];
allPoints.push(getPoint());
var i, cdi;
for(i = cd.length - 1; i > -1; i--) {
cdi = cd[i];
if(cdi.hidden) continue;
var step = cdi.v / totalValues;
sumSteps += step;
allPoints.push(getPoint());
}
var minY = Infinity;
var maxY = -Infinity;
for(i = 0; i < allPoints.length; i++) {
p = allPoints[i];
minY = Math.min(minY, p[1]);
maxY = Math.max(maxY, p[1]);
}
// center the shape
for(i = 0; i < allPoints.length; i++) {
allPoints[i][1] -= (maxY + minY) / 2;
}
var lastX = allPoints[allPoints.length - 1][0];
// get pie r
var r = cd0.r;
var rY = (maxY - minY) / 2;
var scaleX = r / lastX;
var scaleY = r / rY * aspectratio;
// set funnelarea r
cd0.r = scaleY * rY;
// scale the shape
for(i = 0; i < allPoints.length; i++) {
allPoints[i][0] *= scaleX;
allPoints[i][1] *= scaleY;
}
// record first position
p = allPoints[0];
var prevLeft = [-p[0], p[1]];
var prevRight = [p[0], p[1]];
var n = 0; // note we skip the very first point.
for(i = cd.length - 1; i > -1; i--) {
cdi = cd[i];
if(cdi.hidden) continue;
n += 1;
var x = allPoints[n][0];
var y = allPoints[n][1];
cdi.TL = [-x, y];
cdi.TR = [x, y];
cdi.BL = prevLeft;
cdi.BR = prevRight;
cdi.pxmid = getBetween(cdi.TR, cdi.BR);
prevLeft = cdi.TL;
prevRight = cdi.TR;
}
}
},{"../../components/drawing":388,"../../lib":503,"../../lib/svg_text_utils":529,"../bar/plot":659,"../bar/uniform_text":664,"../pie/helpers":904,"../pie/plot":908,"@plotly/d3":58}],791:[function(_dereq_,module,exports){
'use strict';
var d3 = _dereq_('@plotly/d3');
var styleOne = _dereq_('../pie/style_one');
var resizeText = _dereq_('../bar/uniform_text').resizeText;
module.exports = function style(gd) {
var s = gd._fullLayout._funnelarealayer.selectAll('.trace');
resizeText(gd, s, 'funnelarea');
s.each(function(cd) {
var cd0 = cd[0];
var trace = cd0.trace;
var traceSelection = d3.select(this);
traceSelection.style({opacity: trace.opacity});
traceSelection.selectAll('path.surface').each(function(pt) {
d3.select(this).call(styleOne, pt, trace);
});
});
};
},{"../bar/uniform_text":664,"../pie/style_one":910,"@plotly/d3":58}],792:[function(_dereq_,module,exports){
'use strict';
var scatterAttrs = _dereq_('../scatter/attributes');
var baseAttrs = _dereq_('../../plots/attributes');
var axisHoverFormat = _dereq_('../../plots/cartesian/axis_format_attributes').axisHoverFormat;
var hovertemplateAttrs = _dereq_('../../plots/template_attributes').hovertemplateAttrs;
var colorScaleAttrs = _dereq_('../../components/colorscale/attributes');
var extendFlat = _dereq_('../../lib/extend').extendFlat;
module.exports = extendFlat({
z: {
valType: 'data_array',
editType: 'calc',
},
x: extendFlat({}, scatterAttrs.x, {impliedEdits: {xtype: 'array'}}),
x0: extendFlat({}, scatterAttrs.x0, {impliedEdits: {xtype: 'scaled'}}),
dx: extendFlat({}, scatterAttrs.dx, {impliedEdits: {xtype: 'scaled'}}),
y: extendFlat({}, scatterAttrs.y, {impliedEdits: {ytype: 'array'}}),
y0: extendFlat({}, scatterAttrs.y0, {impliedEdits: {ytype: 'scaled'}}),
dy: extendFlat({}, scatterAttrs.dy, {impliedEdits: {ytype: 'scaled'}}),
xperiod: extendFlat({}, scatterAttrs.xperiod, {impliedEdits: {xtype: 'scaled'}}),
yperiod: extendFlat({}, scatterAttrs.yperiod, {impliedEdits: {ytype: 'scaled'}}),
xperiod0: extendFlat({}, scatterAttrs.xperiod0, {impliedEdits: {xtype: 'scaled'}}),
yperiod0: extendFlat({}, scatterAttrs.yperiod0, {impliedEdits: {ytype: 'scaled'}}),
xperiodalignment: extendFlat({}, scatterAttrs.xperiodalignment, {impliedEdits: {xtype: 'scaled'}}),
yperiodalignment: extendFlat({}, scatterAttrs.yperiodalignment, {impliedEdits: {ytype: 'scaled'}}),
text: {
valType: 'data_array',
editType: 'calc',
},
hovertext: {
valType: 'data_array',
editType: 'calc',
},
transpose: {
valType: 'boolean',
dflt: false,
editType: 'calc',
},
xtype: {
valType: 'enumerated',
values: ['array', 'scaled'],
editType: 'calc+clearAxisTypes',
},
ytype: {
valType: 'enumerated',
values: ['array', 'scaled'],
editType: 'calc+clearAxisTypes',
},
zsmooth: {
valType: 'enumerated',
values: ['fast', 'best', false],
dflt: false,
editType: 'calc',
},
hoverongaps: {
valType: 'boolean',
dflt: true,
editType: 'none',
},
connectgaps: {
valType: 'boolean',
editType: 'calc',
},
xgap: {
valType: 'number',
dflt: 0,
min: 0,
editType: 'plot',
},
ygap: {
valType: 'number',
dflt: 0,
min: 0,
editType: 'plot',
},
xhoverformat: axisHoverFormat('x'),
yhoverformat: axisHoverFormat('y'),
zhoverformat: axisHoverFormat('z', 1),
hovertemplate: hovertemplateAttrs(),
showlegend: extendFlat({}, baseAttrs.showlegend, {dflt: false})
}, {
transforms: undefined
},
colorScaleAttrs('', {cLetter: 'z', autoColorDflt: false})
);
},{"../../components/colorscale/attributes":373,"../../lib/extend":493,"../../plots/attributes":550,"../../plots/cartesian/axis_format_attributes":557,"../../plots/template_attributes":633,"../scatter/attributes":925}],793:[function(_dereq_,module,exports){
'use strict';
var Registry = _dereq_('../../registry');
var Lib = _dereq_('../../lib');
var Axes = _dereq_('../../plots/cartesian/axes');
var alignPeriod = _dereq_('../../plots/cartesian/align_period');
var histogram2dCalc = _dereq_('../histogram2d/calc');
var colorscaleCalc = _dereq_('../../components/colorscale/calc');
var convertColumnData = _dereq_('./convert_column_xyz');
var clean2dArray = _dereq_('./clean_2d_array');
var interp2d = _dereq_('./interp2d');
var findEmpties = _dereq_('./find_empties');
var makeBoundArray = _dereq_('./make_bound_array');
var BADNUM = _dereq_('../../constants/numerical').BADNUM;
module.exports = function calc(gd, trace) {
// prepare the raw data
// run makeCalcdata on x and y even for heatmaps, in case of category mappings
var xa = Axes.getFromId(gd, trace.xaxis || 'x');
var ya = Axes.getFromId(gd, trace.yaxis || 'y');
var isContour = Registry.traceIs(trace, 'contour');
var isHist = Registry.traceIs(trace, 'histogram');
var isGL2D = Registry.traceIs(trace, 'gl2d');
var zsmooth = isContour ? 'best' : trace.zsmooth;
var x, x0, dx, origX;
var y, y0, dy, origY;
var z, i, binned;
// cancel minimum tick spacings (only applies to bars and boxes)
xa._minDtick = 0;
ya._minDtick = 0;
if(isHist) {
binned = histogram2dCalc(gd, trace);
origX = binned.orig_x;
x = binned.x;
x0 = binned.x0;
dx = binned.dx;
origY = binned.orig_y;
y = binned.y;
y0 = binned.y0;
dy = binned.dy;
z = binned.z;
} else {
var zIn = trace.z;
if(Lib.isArray1D(zIn)) {
convertColumnData(trace, xa, ya, 'x', 'y', ['z']);
x = trace._x;
y = trace._y;
zIn = trace._z;
} else {
origX = trace.x ? xa.makeCalcdata(trace, 'x') : [];
origY = trace.y ? ya.makeCalcdata(trace, 'y') : [];
x = alignPeriod(trace, xa, 'x', origX).vals;
y = alignPeriod(trace, ya, 'y', origY).vals;
trace._x = x;
trace._y = y;
}
x0 = trace.x0;
dx = trace.dx;
y0 = trace.y0;
dy = trace.dy;
z = clean2dArray(zIn, trace, xa, ya);
}
if(xa.rangebreaks || ya.rangebreaks) {
z = dropZonBreaks(x, y, z);
if(!isHist) {
x = skipBreaks(x);
y = skipBreaks(y);
trace._x = x;
trace._y = y;
}
}
if(!isHist && (isContour || trace.connectgaps)) {
trace._emptypoints = findEmpties(z);
interp2d(z, trace._emptypoints);
}
function noZsmooth(msg) {
zsmooth = trace._input.zsmooth = trace.zsmooth = false;
Lib.warn('cannot use zsmooth: "fast": ' + msg);
}
// check whether we really can smooth (ie all boxes are about the same size)
if(zsmooth === 'fast') {
if(xa.type === 'log' || ya.type === 'log') {
noZsmooth('log axis found');
} else if(!isHist) {
if(x.length) {
var avgdx = (x[x.length - 1] - x[0]) / (x.length - 1);
var maxErrX = Math.abs(avgdx / 100);
for(i = 0; i < x.length - 1; i++) {
if(Math.abs(x[i + 1] - x[i] - avgdx) > maxErrX) {
noZsmooth('x scale is not linear');
break;
}
}
}
if(y.length && zsmooth === 'fast') {
var avgdy = (y[y.length - 1] - y[0]) / (y.length - 1);
var maxErrY = Math.abs(avgdy / 100);
for(i = 0; i < y.length - 1; i++) {
if(Math.abs(y[i + 1] - y[i] - avgdy) > maxErrY) {
noZsmooth('y scale is not linear');
break;
}
}
}
}
}
// create arrays of brick boundaries, to be used by autorange and heatmap.plot
var xlen = Lib.maxRowLength(z);
var xIn = trace.xtype === 'scaled' ? '' : x;
var xArray = makeBoundArray(trace, xIn, x0, dx, xlen, xa);
var yIn = trace.ytype === 'scaled' ? '' : y;
var yArray = makeBoundArray(trace, yIn, y0, dy, z.length, ya);
// handled in gl2d convert step
if(!isGL2D) {
trace._extremes[xa._id] = Axes.findExtremes(xa, xArray);
trace._extremes[ya._id] = Axes.findExtremes(ya, yArray);
}
var cd0 = {
x: xArray,
y: yArray,
z: z,
text: trace._text || trace.text,
hovertext: trace._hovertext || trace.hovertext
};
if(trace.xperiodalignment && origX) {
cd0.orig_x = origX;
}
if(trace.yperiodalignment && origY) {
cd0.orig_y = origY;
}
if(xIn && xIn.length === xArray.length - 1) cd0.xCenter = xIn;
if(yIn && yIn.length === yArray.length - 1) cd0.yCenter = yIn;
if(isHist) {
cd0.xRanges = binned.xRanges;
cd0.yRanges = binned.yRanges;
cd0.pts = binned.pts;
}
if(!isContour) {
colorscaleCalc(gd, trace, {vals: z, cLetter: 'z'});
}
if(isContour && trace.contours && trace.contours.coloring === 'heatmap') {
var dummyTrace = {
type: trace.type === 'contour' ? 'heatmap' : 'histogram2d',
xcalendar: trace.xcalendar,
ycalendar: trace.ycalendar
};
cd0.xfill = makeBoundArray(dummyTrace, xIn, x0, dx, xlen, xa);
cd0.yfill = makeBoundArray(dummyTrace, yIn, y0, dy, z.length, ya);
}
return [cd0];
};
function skipBreaks(a) {
var b = [];
var len = a.length;
for(var i = 0; i < len; i++) {
var v = a[i];
if(v !== BADNUM) b.push(v);
}
return b;
}
function dropZonBreaks(x, y, z) {
var newZ = [];
var k = -1;
for(var i = 0; i < z.length; i++) {
if(y[i] === BADNUM) continue;
k++;
newZ[k] = [];
for(var j = 0; j < z[i].length; j++) {
if(x[j] === BADNUM) continue;
newZ[k].push(z[i][j]);
}
}
return newZ;
}
},{"../../components/colorscale/calc":374,"../../constants/numerical":479,"../../lib":503,"../../plots/cartesian/align_period":551,"../../plots/cartesian/axes":554,"../../registry":638,"../histogram2d/calc":825,"./clean_2d_array":794,"./convert_column_xyz":796,"./find_empties":798,"./interp2d":801,"./make_bound_array":802}],794:[function(_dereq_,module,exports){
'use strict';
var isNumeric = _dereq_('fast-isnumeric');
var Lib = _dereq_('../../lib');
var BADNUM = _dereq_('../../constants/numerical').BADNUM;
module.exports = function clean2dArray(zOld, trace, xa, ya) {
var rowlen, collen, getCollen, old2new, i, j;
function cleanZvalue(v) {
if(!isNumeric(v)) return undefined;
return +v;
}
if(trace && trace.transpose) {
rowlen = 0;
for(i = 0; i < zOld.length; i++) rowlen = Math.max(rowlen, zOld[i].length);
if(rowlen === 0) return false;
getCollen = function(zOld) { return zOld.length; };
old2new = function(zOld, i, j) { return (zOld[j] || [])[i]; };
} else {
rowlen = zOld.length;
getCollen = function(zOld, i) { return zOld[i].length; };
old2new = function(zOld, i, j) { return (zOld[i] || [])[j]; };
}
var padOld2new = function(zOld, i, j) {
if(i === BADNUM || j === BADNUM) return BADNUM;
return old2new(zOld, i, j);
};
function axisMapping(ax) {
if(trace && trace.type !== 'carpet' && trace.type !== 'contourcarpet' &&
ax && ax.type === 'category' && trace['_' + ax._id.charAt(0)].length) {
var axLetter = ax._id.charAt(0);
var axMapping = {};
var traceCategories = trace['_' + axLetter + 'CategoryMap'] || trace[axLetter];
for(i = 0; i < traceCategories.length; i++) {
axMapping[traceCategories[i]] = i;
}
return function(i) {
var ind = axMapping[ax._categories[i]];
return ind + 1 ? ind : BADNUM;
};
} else {
return Lib.identity;
}
}
var xMap = axisMapping(xa);
var yMap = axisMapping(ya);
if(ya && ya.type === 'category') rowlen = ya._categories.length;
var zNew = new Array(rowlen);
for(i = 0; i < rowlen; i++) {
if(xa && xa.type === 'category') {
collen = xa._categories.length;
} else {
collen = getCollen(zOld, i);
}
zNew[i] = new Array(collen);
for(j = 0; j < collen; j++) zNew[i][j] = cleanZvalue(padOld2new(zOld, yMap(i), xMap(j)));
}
return zNew;
};
},{"../../constants/numerical":479,"../../lib":503,"fast-isnumeric":190}],795:[function(_dereq_,module,exports){
'use strict';
module.exports = {
min: 'zmin',
max: 'zmax'
};
},{}],796:[function(_dereq_,module,exports){
'use strict';
var Lib = _dereq_('../../lib');
var BADNUM = _dereq_('../../constants/numerical').BADNUM;
var alignPeriod = _dereq_('../../plots/cartesian/align_period');
module.exports = function convertColumnData(trace, ax1, ax2, var1Name, var2Name, arrayVarNames) {
var colLen = trace._length;
var col1 = ax1.makeCalcdata(trace, var1Name);
var col2 = ax2.makeCalcdata(trace, var2Name);
col1 = alignPeriod(trace, ax1, var1Name, col1).vals;
col2 = alignPeriod(trace, ax2, var2Name, col2).vals;
var textCol = trace.text;
var hasColumnText = (textCol !== undefined && Lib.isArray1D(textCol));
var hoverTextCol = trace.hovertext;
var hasColumnHoverText = (hoverTextCol !== undefined && Lib.isArray1D(hoverTextCol));
var i, j;
var col1dv = Lib.distinctVals(col1);
var col1vals = col1dv.vals;
var col2dv = Lib.distinctVals(col2);
var col2vals = col2dv.vals;
var newArrays = [];
var text;
var hovertext;
var nI = col2vals.length;
var nJ = col1vals.length;
for(i = 0; i < arrayVarNames.length; i++) {
newArrays[i] = Lib.init2dArray(nI, nJ);
}
if(hasColumnText) {
text = Lib.init2dArray(nI, nJ);
}
if(hasColumnHoverText) {
hovertext = Lib.init2dArray(nI, nJ);
}
var after2before = Lib.init2dArray(nI, nJ);
for(i = 0; i < colLen; i++) {
if(col1[i] !== BADNUM && col2[i] !== BADNUM) {
var i1 = Lib.findBin(col1[i] + col1dv.minDiff / 2, col1vals);
var i2 = Lib.findBin(col2[i] + col2dv.minDiff / 2, col2vals);
for(j = 0; j < arrayVarNames.length; j++) {
var arrayVarName = arrayVarNames[j];
var arrayVar = trace[arrayVarName];
var newArray = newArrays[j];
newArray[i2][i1] = arrayVar[i];
after2before[i2][i1] = i;
}
if(hasColumnText) text[i2][i1] = textCol[i];
if(hasColumnHoverText) hovertext[i2][i1] = hoverTextCol[i];
}
}
trace['_' + var1Name] = col1vals;
trace['_' + var2Name] = col2vals;
for(j = 0; j < arrayVarNames.length; j++) {
trace['_' + arrayVarNames[j]] = newArrays[j];
}
if(hasColumnText) trace._text = text;
if(hasColumnHoverText) trace._hovertext = hovertext;
if(ax1 && ax1.type === 'category') {
trace['_' + var1Name + 'CategoryMap'] = col1vals.map(function(v) { return ax1._categories[v];});
}
if(ax2 && ax2.type === 'category') {
trace['_' + var2Name + 'CategoryMap'] = col2vals.map(function(v) { return ax2._categories[v];});
}
trace._after2before = after2before;
};
},{"../../constants/numerical":479,"../../lib":503,"../../plots/cartesian/align_period":551}],797:[function(_dereq_,module,exports){
'use strict';
var Lib = _dereq_('../../lib');
var handleXYZDefaults = _dereq_('./xyz_defaults');
var handlePeriodDefaults = _dereq_('../scatter/period_defaults');
var handleStyleDefaults = _dereq_('./style_defaults');
var colorscaleDefaults = _dereq_('../../components/colorscale/defaults');
var attributes = _dereq_('./attributes');
module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout) {
function coerce(attr, dflt) {
return Lib.coerce(traceIn, traceOut, attributes, attr, dflt);
}
var validData = handleXYZDefaults(traceIn, traceOut, coerce, layout);
if(!validData) {
traceOut.visible = false;
return;
}
handlePeriodDefaults(traceIn, traceOut, layout, coerce);
coerce('xhoverformat');
coerce('yhoverformat');
coerce('text');
coerce('hovertext');
coerce('hovertemplate');
handleStyleDefaults(traceIn, traceOut, coerce, layout);
coerce('hoverongaps');
coerce('connectgaps', Lib.isArray1D(traceOut.z) && (traceOut.zsmooth !== false));
colorscaleDefaults(traceIn, traceOut, layout, coerce, {prefix: '', cLetter: 'z'});
};
},{"../../components/colorscale/defaults":376,"../../lib":503,"../scatter/period_defaults":945,"./attributes":792,"./style_defaults":805,"./xyz_defaults":806}],798:[function(_dereq_,module,exports){
'use strict';
var maxRowLength = _dereq_('../../lib').maxRowLength;
/* Return a list of empty points in 2D array z
* each empty point z[i][j] gives an array [i, j, neighborCount]
* neighborCount is the count of 4 nearest neighbors that DO exist
* this is to give us an order of points to evaluate for interpolation.
* if no neighbors exist, we iteratively look for neighbors that HAVE
* neighbors, and add a fractional neighborCount
*/
module.exports = function findEmpties(z) {
var empties = [];
var neighborHash = {};
var noNeighborList = [];
var nextRow = z[0];
var row = [];
var blank = [0, 0, 0];
var rowLength = maxRowLength(z);
var prevRow;
var i;
var j;
var thisPt;
var p;
var neighborCount;
var newNeighborHash;
var foundNewNeighbors;
for(i = 0; i < z.length; i++) {
prevRow = row;
row = nextRow;
nextRow = z[i + 1] || [];
for(j = 0; j < rowLength; j++) {
if(row[j] === undefined) {
neighborCount = (row[j - 1] !== undefined ? 1 : 0) +
(row[j + 1] !== undefined ? 1 : 0) +
(prevRow[j] !== undefined ? 1 : 0) +
(nextRow[j] !== undefined ? 1 : 0);
if(neighborCount) {
// for this purpose, don't count off-the-edge points
// as undefined neighbors
if(i === 0) neighborCount++;
if(j === 0) neighborCount++;
if(i === z.length - 1) neighborCount++;
if(j === row.length - 1) neighborCount++;
// if all neighbors that could exist do, we don't
// need this for finding farther neighbors
if(neighborCount < 4) {
neighborHash[[i, j]] = [i, j, neighborCount];
}
empties.push([i, j, neighborCount]);
} else noNeighborList.push([i, j]);
}
}
}
while(noNeighborList.length) {
newNeighborHash = {};
foundNewNeighbors = false;
// look for cells that now have neighbors but didn't before
for(p = noNeighborList.length - 1; p >= 0; p--) {
thisPt = noNeighborList[p];
i = thisPt[0];
j = thisPt[1];
neighborCount = ((neighborHash[[i - 1, j]] || blank)[2] +
(neighborHash[[i + 1, j]] || blank)[2] +
(neighborHash[[i, j - 1]] || blank)[2] +
(neighborHash[[i, j + 1]] || blank)[2]) / 20;
if(neighborCount) {
newNeighborHash[thisPt] = [i, j, neighborCount];
noNeighborList.splice(p, 1);
foundNewNeighbors = true;
}
}
if(!foundNewNeighbors) {
throw 'findEmpties iterated with no new neighbors';
}
// put these new cells into the main neighbor list
for(thisPt in newNeighborHash) {
neighborHash[thisPt] = newNeighborHash[thisPt];
empties.push(newNeighborHash[thisPt]);
}
}
// sort the full list in descending order of neighbor count
return empties.sort(function(a, b) { return b[2] - a[2]; });
};
},{"../../lib":503}],799:[function(_dereq_,module,exports){
'use strict';
var Fx = _dereq_('../../components/fx');
var Lib = _dereq_('../../lib');
var Axes = _dereq_('../../plots/cartesian/axes');
var extractOpts = _dereq_('../../components/colorscale').extractOpts;
module.exports = function hoverPoints(pointData, xval, yval, hovermode, opts) {
if(!opts) opts = {};
var isContour = opts.isContour;
var cd0 = pointData.cd[0];
var trace = cd0.trace;
var xa = pointData.xa;
var ya = pointData.ya;
var x = cd0.x;
var y = cd0.y;
var z = cd0.z;
var xc = cd0.xCenter;
var yc = cd0.yCenter;
var zmask = cd0.zmask;
var zhoverformat = trace.zhoverformat;
var x2 = x;
var y2 = y;
var xl, yl, nx, ny;
if(pointData.index !== false) {
try {
nx = Math.round(pointData.index[1]);
ny = Math.round(pointData.index[0]);
} catch(e) {
Lib.error('Error hovering on heatmap, ' +
'pointNumber must be [row,col], found:', pointData.index);
return;
}
if(nx < 0 || nx >= z[0].length || ny < 0 || ny > z.length) {
return;
}
} else if(Fx.inbox(xval - x[0], xval - x[x.length - 1], 0) > 0 ||
Fx.inbox(yval - y[0], yval - y[y.length - 1], 0) > 0) {
return;
} else {
if(isContour) {
var i2;
x2 = [2 * x[0] - x[1]];
for(i2 = 1; i2 < x.length; i2++) {
x2.push((x[i2] + x[i2 - 1]) / 2);
}
x2.push([2 * x[x.length - 1] - x[x.length - 2]]);
y2 = [2 * y[0] - y[1]];
for(i2 = 1; i2 < y.length; i2++) {
y2.push((y[i2] + y[i2 - 1]) / 2);
}
y2.push([2 * y[y.length - 1] - y[y.length - 2]]);
}
nx = Math.max(0, Math.min(x2.length - 2, Lib.findBin(xval, x2)));
ny = Math.max(0, Math.min(y2.length - 2, Lib.findBin(yval, y2)));
}
var x0 = xa.c2p(x[nx]);
var x1 = xa.c2p(x[nx + 1]);
var y0 = ya.c2p(y[ny]);
var y1 = ya.c2p(y[ny + 1]);
var _x, _y;
if(isContour) {
_x = cd0.orig_x || x;
_y = cd0.orig_y || y;
x1 = x0;
xl = _x[nx];
y1 = y0;
yl = _y[ny];
} else {
_x = cd0.orig_x || xc || x;
_y = cd0.orig_y || yc || y;
xl = xc ? _x[nx] : ((_x[nx] + _x[nx + 1]) / 2);
yl = yc ? _y[ny] : ((_y[ny] + _y[ny + 1]) / 2);
if(xa && xa.type === 'category') xl = x[nx];
if(ya && ya.type === 'category') yl = y[ny];
if(trace.zsmooth) {
x0 = x1 = xa.c2p(xl);
y0 = y1 = ya.c2p(yl);
}
}
var zVal = z[ny][nx];
if(zmask && !zmask[ny][nx]) zVal = undefined;
if(zVal === undefined && !trace.hoverongaps) return;
var text;
if(Array.isArray(cd0.hovertext) && Array.isArray(cd0.hovertext[ny])) {
text = cd0.hovertext[ny][nx];
} else if(Array.isArray(cd0.text) && Array.isArray(cd0.text[ny])) {
text = cd0.text[ny][nx];
}
// dummy axis for formatting the z value
var cOpts = extractOpts(trace);
var dummyAx = {
type: 'linear',
range: [cOpts.min, cOpts.max],
hoverformat: zhoverformat,
_separators: xa._separators,
_numFormat: xa._numFormat
};
var zLabel = Axes.tickText(dummyAx, zVal, 'hover').text;
return [Lib.extendFlat(pointData, {
index: trace._after2before ? trace._after2before[ny][nx] : [ny, nx],
// never let a 2D override 1D type as closest point
distance: pointData.maxHoverDistance,
spikeDistance: pointData.maxSpikeDistance,
x0: x0,
x1: x1,
y0: y0,
y1: y1,
xLabelVal: xl,
yLabelVal: yl,
zLabelVal: zVal,
zLabel: zLabel,
text: text
})];
};
},{"../../components/colorscale":378,"../../components/fx":406,"../../lib":503,"../../plots/cartesian/axes":554}],800:[function(_dereq_,module,exports){
'use strict';
module.exports = {
attributes: _dereq_('./attributes'),
supplyDefaults: _dereq_('./defaults'),
calc: _dereq_('./calc'),
plot: _dereq_('./plot'),
colorbar: _dereq_('./colorbar'),
style: _dereq_('./style'),
hoverPoints: _dereq_('./hover'),
moduleType: 'trace',
name: 'heatmap',
basePlotModule: _dereq_('../../plots/cartesian'),
categories: ['cartesian', 'svg', '2dMap', 'showLegend'],
meta: {
}
};
},{"../../plots/cartesian":568,"./attributes":792,"./calc":793,"./colorbar":795,"./defaults":797,"./hover":799,"./plot":803,"./style":804}],801:[function(_dereq_,module,exports){
'use strict';
var Lib = _dereq_('../../lib');
var INTERPTHRESHOLD = 1e-2;
var NEIGHBORSHIFTS = [[-1, 0], [1, 0], [0, -1], [0, 1]];
function correctionOvershoot(maxFractionalChange) {
// start with less overshoot, until we know it's converging,
// then ramp up the overshoot for faster convergence
return 0.5 - 0.25 * Math.min(1, maxFractionalChange * 0.5);
}
/*
* interp2d: Fill in missing data from a 2D array using an iterative
* poisson equation solver with zero-derivative BC at edges.
* Amazingly, this just amounts to repeatedly averaging all the existing
* nearest neighbors, at least if we don't take x/y scaling into account,
* which is the right approach here where x and y may not even have the
* same units.
*
* @param {array of arrays} z
* The 2D array to fill in. Will be mutated here. Assumed to already be
* cleaned, so all entries are numbers except gaps, which are `undefined`.
* @param {array of arrays} emptyPoints
* Each entry [i, j, neighborCount] for empty points z[i][j] and the number
* of neighbors that are *not* missing. Assumed to be sorted from most to
* least neighbors, as produced by heatmap/find_empties.
*/
module.exports = function interp2d(z, emptyPoints) {
var maxFractionalChange = 1;
var i;
// one pass to fill in a starting value for all the empties
iterateInterp2d(z, emptyPoints);
// we're don't need to iterate lone empties - remove them
for(i = 0; i < emptyPoints.length; i++) {
if(emptyPoints[i][2] < 4) break;
}
// but don't remove these points from the original array,
// we'll use them for masking, so make a copy.
emptyPoints = emptyPoints.slice(i);
for(i = 0; i < 100 && maxFractionalChange > INTERPTHRESHOLD; i++) {
maxFractionalChange = iterateInterp2d(z, emptyPoints,
correctionOvershoot(maxFractionalChange));
}
if(maxFractionalChange > INTERPTHRESHOLD) {
Lib.log('interp2d didn\'t converge quickly', maxFractionalChange);
}
return z;
};
function iterateInterp2d(z, emptyPoints, overshoot) {
var maxFractionalChange = 0;
var thisPt;
var i;
var j;
var p;
var q;
var neighborShift;
var neighborRow;
var neighborVal;
var neighborCount;
var neighborSum;
var initialVal;
var minNeighbor;
var maxNeighbor;
for(p = 0; p < emptyPoints.length; p++) {
thisPt = emptyPoints[p];
i = thisPt[0];
j = thisPt[1];
initialVal = z[i][j];
neighborSum = 0;
neighborCount = 0;
for(q = 0; q < 4; q++) {
neighborShift = NEIGHBORSHIFTS[q];
neighborRow = z[i + neighborShift[0]];
if(!neighborRow) continue;
neighborVal = neighborRow[j + neighborShift[1]];
if(neighborVal !== undefined) {
if(neighborSum === 0) {
minNeighbor = maxNeighbor = neighborVal;
} else {
minNeighbor = Math.min(minNeighbor, neighborVal);
maxNeighbor = Math.max(maxNeighbor, neighborVal);
}
neighborCount++;
neighborSum += neighborVal;
}
}
if(neighborCount === 0) {
throw 'iterateInterp2d order is wrong: no defined neighbors';
}
// this is the laplace equation interpolation:
// each point is just the average of its neighbors
// note that this ignores differential x/y scaling
// which I think is the right approach, since we
// don't know what that scaling means
z[i][j] = neighborSum / neighborCount;
if(initialVal === undefined) {
if(neighborCount < 4) maxFractionalChange = 1;
} else {
// we can make large empty regions converge faster
// if we overshoot the change vs the previous value
z[i][j] = (1 + overshoot) * z[i][j] - overshoot * initialVal;
if(maxNeighbor > minNeighbor) {
maxFractionalChange = Math.max(maxFractionalChange,
Math.abs(z[i][j] - initialVal) / (maxNeighbor - minNeighbor));
}
}
}
return maxFractionalChange;
}
},{"../../lib":503}],802:[function(_dereq_,module,exports){
'use strict';
var Registry = _dereq_('../../registry');
var isArrayOrTypedArray = _dereq_('../../lib').isArrayOrTypedArray;
module.exports = function makeBoundArray(trace, arrayIn, v0In, dvIn, numbricks, ax) {
var arrayOut = [];
var isContour = Registry.traceIs(trace, 'contour');
var isHist = Registry.traceIs(trace, 'histogram');
var isGL2D = Registry.traceIs(trace, 'gl2d');
var v0;
var dv;
var i;
var isArrayOfTwoItemsOrMore = isArrayOrTypedArray(arrayIn) && arrayIn.length > 1;
if(isArrayOfTwoItemsOrMore && !isHist && (ax.type !== 'category')) {
var len = arrayIn.length;
// given vals are brick centers
// hopefully length === numbricks, but use this method even if too few are supplied
// and extend it linearly based on the last two points
if(len <= numbricks) {
// contour plots only want the centers
if(isContour || isGL2D) arrayOut = arrayIn.slice(0, numbricks);
else if(numbricks === 1) {
arrayOut = [arrayIn[0] - 0.5, arrayIn[0] + 0.5];
} else {
arrayOut = [1.5 * arrayIn[0] - 0.5 * arrayIn[1]];
for(i = 1; i < len; i++) {
arrayOut.push((arrayIn[i - 1] + arrayIn[i]) * 0.5);
}
arrayOut.push(1.5 * arrayIn[len - 1] - 0.5 * arrayIn[len - 2]);
}
if(len < numbricks) {
var lastPt = arrayOut[arrayOut.length - 1];
var delta = lastPt - arrayOut[arrayOut.length - 2];
for(i = len; i < numbricks; i++) {
lastPt += delta;
arrayOut.push(lastPt);
}
}
} else {
// hopefully length === numbricks+1, but do something regardless:
// given vals are brick boundaries
return isContour ?
arrayIn.slice(0, numbricks) : // we must be strict for contours
arrayIn.slice(0, numbricks + 1);
}
} else {
var calendar = trace[ax._id.charAt(0) + 'calendar'];
if(isHist) {
v0 = ax.r2c(v0In, 0, calendar);
} else {
if(isArrayOrTypedArray(arrayIn) && arrayIn.length === 1) {
v0 = arrayIn[0];
} else if(v0In === undefined) {
v0 = 0;
} else {
var fn = ax.type === 'log' ? ax.d2c : ax.r2c;
v0 = fn(v0In, 0, calendar);
}
}
dv = dvIn || 1;
for(i = (isContour || isGL2D) ? 0 : -0.5; i < numbricks; i++) {
arrayOut.push(v0 + dv * i);
}
}
return arrayOut;
};
},{"../../lib":503,"../../registry":638}],803:[function(_dereq_,module,exports){
'use strict';
var d3 = _dereq_('@plotly/d3');
var tinycolor = _dereq_('tinycolor2');
var Registry = _dereq_('../../registry');
var Lib = _dereq_('../../lib');
var makeColorScaleFuncFromTrace = _dereq_('../../components/colorscale').makeColorScaleFuncFromTrace;
var xmlnsNamespaces = _dereq_('../../constants/xmlns_namespaces');
module.exports = function(gd, plotinfo, cdheatmaps, heatmapLayer) {
var xa = plotinfo.xaxis;
var ya = plotinfo.yaxis;
Lib.makeTraceGroups(heatmapLayer, cdheatmaps, 'hm').each(function(cd) {
var plotGroup = d3.select(this);
var cd0 = cd[0];
var trace = cd0.trace;
var z = cd0.z;
var x = cd0.x;
var y = cd0.y;
var xc = cd0.xCenter;
var yc = cd0.yCenter;
var isContour = Registry.traceIs(trace, 'contour');
var zsmooth = isContour ? 'best' : trace.zsmooth;
// get z dims
var m = z.length;
var n = Lib.maxRowLength(z);
var xrev = false;
var yrev = false;
var left, right, temp, top, bottom, i;
// TODO: if there are multiple overlapping categorical heatmaps,
// or if we allow category sorting, then the categories may not be
// sequential... may need to reorder and/or expand z
// Get edges of png in pixels (xa.c2p() maps axes coordinates to pixel coordinates)
// figure out if either axis is reversed (y is usually reversed, in pixel coords)
// also clip the image to maximum 50% outside the visible plot area
// bigger image lets you pan more naturally, but slows performance.
// TODO: use low-resolution images outside the visible plot for panning
// these while loops find the first and last brick bounds that are defined
// (in case of log of a negative)
i = 0;
while(left === undefined && i < x.length - 1) {
left = xa.c2p(x[i]);
i++;
}
i = x.length - 1;
while(right === undefined && i > 0) {
right = xa.c2p(x[i]);
i--;
}
if(right < left) {
temp = right;
right = left;
left = temp;
xrev = true;
}
i = 0;
while(top === undefined && i < y.length - 1) {
top = ya.c2p(y[i]);
i++;
}
i = y.length - 1;
while(bottom === undefined && i > 0) {
bottom = ya.c2p(y[i]);
i--;
}
if(bottom < top) {
temp = top;
top = bottom;
bottom = temp;
yrev = true;
}
// for contours with heatmap fill, we generate the boundaries based on
// brick centers but then use the brick edges for drawing the bricks
if(isContour) {
xc = x;
yc = y;
x = cd0.xfill;
y = cd0.yfill;
}
// make an image that goes at most half a screen off either side, to keep
// time reasonable when you zoom in. if zsmooth is true/fast, don't worry
// about this, because zooming doesn't increase number of pixels
// if zsmooth is best, don't include anything off screen because it takes too long
if(zsmooth !== 'fast') {
var extra = zsmooth === 'best' ? 0 : 0.5;
left = Math.max(-extra * xa._length, left);
right = Math.min((1 + extra) * xa._length, right);
top = Math.max(-extra * ya._length, top);
bottom = Math.min((1 + extra) * ya._length, bottom);
}
var imageWidth = Math.round(right - left);
var imageHeight = Math.round(bottom - top);
// setup image nodes
// if image is entirely off-screen, don't even draw it
var isOffScreen = (imageWidth <= 0 || imageHeight <= 0);
if(isOffScreen) {
var noImage = plotGroup.selectAll('image').data([]);
noImage.exit().remove();
return;
}
// generate image data
var canvasW, canvasH;
if(zsmooth === 'fast') {
canvasW = n;
canvasH = m;
} else {
canvasW = imageWidth;
canvasH = imageHeight;
}
var canvas = document.createElement('canvas');
canvas.width = canvasW;
canvas.height = canvasH;
var context = canvas.getContext('2d');
var sclFunc = makeColorScaleFuncFromTrace(trace, {noNumericCheck: true, returnArray: true});
// map brick boundaries to image pixels
var xpx,
ypx;
if(zsmooth === 'fast') {
xpx = xrev ?
function(index) { return n - 1 - index; } :
Lib.identity;
ypx = yrev ?
function(index) { return m - 1 - index; } :
Lib.identity;
} else {
xpx = function(index) {
return Lib.constrain(Math.round(xa.c2p(x[index]) - left),
0, imageWidth);
};
ypx = function(index) {
return Lib.constrain(Math.round(ya.c2p(y[index]) - top),
0, imageHeight);
};
}
// build the pixel map brick-by-brick
// cruise through z-matrix row-by-row
// build a brick at each z-matrix value
var yi = ypx(0);
var yb = [yi, yi];
var xbi = xrev ? 0 : 1;
var ybi = yrev ? 0 : 1;
// for collecting an average luminosity of the heatmap
var pixcount = 0;
var rcount = 0;
var gcount = 0;
var bcount = 0;
var xb, j, xi, v, row, c;
function setColor(v, pixsize) {
if(v !== undefined) {
var c = sclFunc(v);
c[0] = Math.round(c[0]);
c[1] = Math.round(c[1]);
c[2] = Math.round(c[2]);
pixcount += pixsize;
rcount += c[0] * pixsize;
gcount += c[1] * pixsize;
bcount += c[2] * pixsize;
return c;
}
return [0, 0, 0, 0];
}
function interpColor(r0, r1, xinterp, yinterp) {
var z00 = r0[xinterp.bin0];
if(z00 === undefined) return setColor(undefined, 1);
var z01 = r0[xinterp.bin1];
var z10 = r1[xinterp.bin0];
var z11 = r1[xinterp.bin1];
var dx = (z01 - z00) || 0;
var dy = (z10 - z00) || 0;
var dxy;
// the bilinear interpolation term needs different calculations
// for all the different permutations of missing data
// among the neighbors of the main point, to ensure
// continuity across brick boundaries.
if(z01 === undefined) {
if(z11 === undefined) dxy = 0;
else if(z10 === undefined) dxy = 2 * (z11 - z00);
else dxy = (2 * z11 - z10 - z00) * 2 / 3;
} else if(z11 === undefined) {
if(z10 === undefined) dxy = 0;
else dxy = (2 * z00 - z01 - z10) * 2 / 3;
} else if(z10 === undefined) dxy = (2 * z11 - z01 - z00) * 2 / 3;
else dxy = (z11 + z00 - z01 - z10);
return setColor(z00 + xinterp.frac * dx + yinterp.frac * (dy + xinterp.frac * dxy));
}
if(zsmooth) { // best or fast, works fastest with imageData
var pxIndex = 0;
var pixels;
try {
pixels = new Uint8Array(imageWidth * imageHeight * 4);
} catch(e) {
pixels = new Array(imageWidth * imageHeight * 4);
}
if(zsmooth === 'best') {
var xForPx = xc || x;
var yForPx = yc || y;
var xPixArray = new Array(xForPx.length);
var yPixArray = new Array(yForPx.length);
var xinterpArray = new Array(imageWidth);
var findInterpX = xc ? findInterpFromCenters : findInterp;
var findInterpY = yc ? findInterpFromCenters : findInterp;
var yinterp, r0, r1;
// first make arrays of x and y pixel locations of brick boundaries
for(i = 0; i < xForPx.length; i++) xPixArray[i] = Math.round(xa.c2p(xForPx[i]) - left);
for(i = 0; i < yForPx.length; i++) yPixArray[i] = Math.round(ya.c2p(yForPx[i]) - top);
// then make arrays of interpolations
// (bin0=closest, bin1=next, frac=fractional dist.)
for(i = 0; i < imageWidth; i++) xinterpArray[i] = findInterpX(i, xPixArray);
// now do the interpolations and fill the png
for(j = 0; j < imageHeight; j++) {
yinterp = findInterpY(j, yPixArray);
r0 = z[yinterp.bin0];
r1 = z[yinterp.bin1];
for(i = 0; i < imageWidth; i++, pxIndex += 4) {
c = interpColor(r0, r1, xinterpArray[i], yinterp);
putColor(pixels, pxIndex, c);
}
}
} else { // zsmooth = fast
for(j = 0; j < m; j++) {
row = z[j];
yb = ypx(j);
for(i = 0; i < imageWidth; i++) {
c = setColor(row[i], 1);
pxIndex = (yb * imageWidth + xpx(i)) * 4;
putColor(pixels, pxIndex, c);
}
}
}
var imageData = context.createImageData(imageWidth, imageHeight);
try {
imageData.data.set(pixels);
} catch(e) {
var pxArray = imageData.data;
var dlen = pxArray.length;
for(j = 0; j < dlen; j ++) {
pxArray[j] = pixels[j];
}
}
context.putImageData(imageData, 0, 0);
} else { // zsmooth = false -> filling potentially large bricks works fastest with fillRect
// gaps do not need to be exact integers, but if they *are* we will get
// cleaner edges by rounding at least one edge
var xGap = trace.xgap;
var yGap = trace.ygap;
var xGapLeft = Math.floor(xGap / 2);
var yGapTop = Math.floor(yGap / 2);
for(j = 0; j < m; j++) {
row = z[j];
yb.reverse();
yb[ybi] = ypx(j + 1);
if(yb[0] === yb[1] || yb[0] === undefined || yb[1] === undefined) {
continue;
}
xi = xpx(0);
xb = [xi, xi];
for(i = 0; i < n; i++) {
// build one color brick!
xb.reverse();
xb[xbi] = xpx(i + 1);
if(xb[0] === xb[1] || xb[0] === undefined || xb[1] === undefined) {
continue;
}
v = row[i];
c = setColor(v, (xb[1] - xb[0]) * (yb[1] - yb[0]));
context.fillStyle = 'rgba(' + c.join(',') + ')';
context.fillRect(xb[0] + xGapLeft, yb[0] + yGapTop,
xb[1] - xb[0] - xGap, yb[1] - yb[0] - yGap);
}
}
}
rcount = Math.round(rcount / pixcount);
gcount = Math.round(gcount / pixcount);
bcount = Math.round(bcount / pixcount);
var avgColor = tinycolor('rgb(' + rcount + ',' + gcount + ',' + bcount + ')');
gd._hmpixcount = (gd._hmpixcount||0) + pixcount;
gd._hmlumcount = (gd._hmlumcount||0) + pixcount * avgColor.getLuminance();
var image3 = plotGroup.selectAll('image')
.data(cd);
image3.enter().append('svg:image').attr({
xmlns: xmlnsNamespaces.svg,
preserveAspectRatio: 'none'
});
image3.attr({
height: imageHeight,
width: imageWidth,
x: left,
y: top,
'xlink:href': canvas.toDataURL('image/png')
});
});
};
// get interpolated bin value. Returns {bin0:closest bin, frac:fractional dist to next, bin1:next bin}
function findInterp(pixel, pixArray) {
var maxBin = pixArray.length - 2;
var bin = Lib.constrain(Lib.findBin(pixel, pixArray), 0, maxBin);
var pix0 = pixArray[bin];
var pix1 = pixArray[bin + 1];
var interp = Lib.constrain(bin + (pixel - pix0) / (pix1 - pix0) - 0.5, 0, maxBin);
var bin0 = Math.round(interp);
var frac = Math.abs(interp - bin0);
if(!interp || interp === maxBin || !frac) {
return {
bin0: bin0,
bin1: bin0,
frac: 0
};
}
return {
bin0: bin0,
frac: frac,
bin1: Math.round(bin0 + frac / (interp - bin0))
};
}
function findInterpFromCenters(pixel, centerPixArray) {
var maxBin = centerPixArray.length - 1;
var bin = Lib.constrain(Lib.findBin(pixel, centerPixArray), 0, maxBin);
var pix0 = centerPixArray[bin];
var pix1 = centerPixArray[bin + 1];
var frac = ((pixel - pix0) / (pix1 - pix0)) || 0;
if(frac <= 0) {
return {
bin0: bin,
bin1: bin,
frac: 0
};
}
if(frac < 0.5) {
return {
bin0: bin,
bin1: bin + 1,
frac: frac
};
}
return {
bin0: bin + 1,
bin1: bin,
frac: 1 - frac
};
}
function putColor(pixels, pxIndex, c) {
pixels[pxIndex] = c[0];
pixels[pxIndex + 1] = c[1];
pixels[pxIndex + 2] = c[2];
pixels[pxIndex + 3] = Math.round(c[3] * 255);
}
},{"../../components/colorscale":378,"../../constants/xmlns_namespaces":480,"../../lib":503,"../../registry":638,"@plotly/d3":58,"tinycolor2":312}],804:[function(_dereq_,module,exports){
'use strict';
var d3 = _dereq_('@plotly/d3');
module.exports = function style(gd) {
d3.select(gd).selectAll('.hm image')
.style('opacity', function(d) {
return d.trace.opacity;
});
};
},{"@plotly/d3":58}],805:[function(_dereq_,module,exports){
'use strict';
module.exports = function handleStyleDefaults(traceIn, traceOut, coerce) {
var zsmooth = coerce('zsmooth');
if(zsmooth === false) {
// ensure that xgap and ygap are coerced only when zsmooth allows them to have an effect.
coerce('xgap');
coerce('ygap');
}
coerce('zhoverformat');
};
},{}],806:[function(_dereq_,module,exports){
'use strict';
var isNumeric = _dereq_('fast-isnumeric');
var Lib = _dereq_('../../lib');
var Registry = _dereq_('../../registry');
module.exports = function handleXYZDefaults(traceIn, traceOut, coerce, layout, xName, yName) {
var z = coerce('z');
xName = xName || 'x';
yName = yName || 'y';
var x, y;
if(z === undefined || !z.length) return 0;
if(Lib.isArray1D(traceIn.z)) {
x = coerce(xName);
y = coerce(yName);
var xlen = Lib.minRowLength(x);
var ylen = Lib.minRowLength(y);
// column z must be accompanied by xName and yName arrays
if(xlen === 0 || ylen === 0) return 0;
traceOut._length = Math.min(xlen, ylen, z.length);
} else {
x = coordDefaults(xName, coerce);
y = coordDefaults(yName, coerce);
// TODO put z validation elsewhere
if(!isValidZ(z)) return 0;
coerce('transpose');
traceOut._length = null;
}
if(traceIn.type === 'heatmapgl') return true; // skip calendars until we handle them in those traces
var handleCalendarDefaults = Registry.getComponentMethod('calendars', 'handleTraceDefaults');
handleCalendarDefaults(traceIn, traceOut, [xName, yName], layout);
return true;
};
function coordDefaults(coordStr, coerce) {
var coord = coerce(coordStr);
var coordType = coord ? coerce(coordStr + 'type', 'array') : 'scaled';
if(coordType === 'scaled') {
coerce(coordStr + '0');
coerce('d' + coordStr);
}
return coord;
}
function isValidZ(z) {
var allRowsAreArrays = true;
var oneRowIsFilled = false;
var hasOneNumber = false;
var zi;
/*
* Without this step:
*
* hasOneNumber = false breaks contour but not heatmap
* allRowsAreArrays = false breaks contour but not heatmap
* oneRowIsFilled = false breaks both
*/
for(var i = 0; i < z.length; i++) {
zi = z[i];
if(!Lib.isArrayOrTypedArray(zi)) {
allRowsAreArrays = false;
break;
}
if(zi.length > 0) oneRowIsFilled = true;
for(var j = 0; j < zi.length; j++) {
if(isNumeric(zi[j])) {
hasOneNumber = true;
break;
}
}
}
return (allRowsAreArrays && oneRowIsFilled && hasOneNumber);
}
},{"../../lib":503,"../../registry":638,"fast-isnumeric":190}],807:[function(_dereq_,module,exports){
'use strict';
var heatmapAttrs = _dereq_('../heatmap/attributes');
var colorScaleAttrs = _dereq_('../../components/colorscale/attributes');
var extendFlat = _dereq_('../../lib/extend').extendFlat;
var overrideAll = _dereq_('../../plot_api/edit_types').overrideAll;
var commonList = [
'z',
'x', 'x0', 'dx',
'y', 'y0', 'dy',
'text', 'transpose',
'xtype', 'ytype'
];
var attrs = {};
for(var i = 0; i < commonList.length; i++) {
var k = commonList[i];
attrs[k] = heatmapAttrs[k];
}
attrs.zsmooth = {
valType: 'enumerated',
values: ['fast', false],
dflt: 'fast',
editType: 'calc',
};
extendFlat(
attrs,
colorScaleAttrs('', {cLetter: 'z', autoColorDflt: false})
);
module.exports = overrideAll(attrs, 'calc', 'nested');
},{"../../components/colorscale/attributes":373,"../../lib/extend":493,"../../plot_api/edit_types":536,"../heatmap/attributes":792}],808:[function(_dereq_,module,exports){
'use strict';
var createHeatmap2D = _dereq_('../../../stackgl_modules').gl_heatmap2d;
var Axes = _dereq_('../../plots/cartesian/axes');
var str2RGBArray = _dereq_('../../lib/str2rgbarray');
function Heatmap(scene, uid) {
this.scene = scene;
this.uid = uid;
this.type = 'heatmapgl';
this.name = '';
this.hoverinfo = 'all';
this.xData = [];
this.yData = [];
this.zData = [];
this.textLabels = [];
this.idToIndex = [];
this.bounds = [0, 0, 0, 0];
this.options = {
zsmooth: 'fast',
z: [],
x: [],
y: [],
shape: [0, 0],
colorLevels: [0],
colorValues: [0, 0, 0, 1]
};
this.heatmap = createHeatmap2D(scene.glplot, this.options);
this.heatmap._trace = this;
}
var proto = Heatmap.prototype;
proto.handlePick = function(pickResult) {
var options = this.options;
var shape = options.shape;
var index = pickResult.pointId;
var xIndex = index % shape[0];
var yIndex = Math.floor(index / shape[0]);
var zIndex = index;
return {
trace: this,
dataCoord: pickResult.dataCoord,
traceCoord: [
options.x[xIndex],
options.y[yIndex],
options.z[zIndex]
],
textLabel: this.textLabels[index],
name: this.name,
pointIndex: [yIndex, xIndex],
hoverinfo: this.hoverinfo
};
};
proto.update = function(fullTrace, calcTrace) {
var calcPt = calcTrace[0];
this.index = fullTrace.index;
this.name = fullTrace.name;
this.hoverinfo = fullTrace.hoverinfo;
// convert z from 2D -> 1D
var z = calcPt.z;
this.options.z = [].concat.apply([], z);
var rowLen = z[0].length;
var colLen = z.length;
this.options.shape = [rowLen, colLen];
this.options.x = calcPt.x;
this.options.y = calcPt.y;
this.options.zsmooth = fullTrace.zsmooth;
var colorOptions = convertColorscale(fullTrace);
this.options.colorLevels = colorOptions.colorLevels;
this.options.colorValues = colorOptions.colorValues;
// convert text from 2D -> 1D
this.textLabels = [].concat.apply([], fullTrace.text);
this.heatmap.update(this.options);
var xa = this.scene.xaxis;
var ya = this.scene.yaxis;
var xOpts, yOpts;
if(fullTrace.zsmooth === false) {
// increase padding for discretised heatmap as suggested by Louise Ord
xOpts = { ppad: calcPt.x[1] - calcPt.x[0] };
yOpts = { ppad: calcPt.y[1] - calcPt.y[0] };
}
fullTrace._extremes[xa._id] = Axes.findExtremes(xa, calcPt.x, xOpts);
fullTrace._extremes[ya._id] = Axes.findExtremes(ya, calcPt.y, yOpts);
};
proto.dispose = function() {
this.heatmap.dispose();
};
function convertColorscale(fullTrace) {
var scl = fullTrace.colorscale;
var zmin = fullTrace.zmin;
var zmax = fullTrace.zmax;
var N = scl.length;
var domain = new Array(N);
var range = new Array(4 * N);
for(var i = 0; i < N; i++) {
var si = scl[i];
var color = str2RGBArray(si[1]);
domain[i] = zmin + si[0] * (zmax - zmin);
for(var j = 0; j < 4; j++) {
range[(4 * i) + j] = color[j];
}
}
return {
colorLevels: domain,
colorValues: range
};
}
function createHeatmap(scene, fullTrace, calcTrace) {
var plot = new Heatmap(scene, fullTrace.uid);
plot.update(fullTrace, calcTrace);
return plot;
}
module.exports = createHeatmap;
},{"../../../stackgl_modules":1119,"../../lib/str2rgbarray":528,"../../plots/cartesian/axes":554}],809:[function(_dereq_,module,exports){
'use strict';
var Lib = _dereq_('../../lib');
var handleXYZDefaults = _dereq_('../heatmap/xyz_defaults');
var colorscaleDefaults = _dereq_('../../components/colorscale/defaults');
var attributes = _dereq_('./attributes');
module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout) {
function coerce(attr, dflt) {
return Lib.coerce(traceIn, traceOut, attributes, attr, dflt);
}
var validData = handleXYZDefaults(traceIn, traceOut, coerce, layout);
if(!validData) {
traceOut.visible = false;
return;
}
coerce('text');
coerce('zsmooth');
colorscaleDefaults(traceIn, traceOut, layout, coerce, {prefix: '', cLetter: 'z'});
};
},{"../../components/colorscale/defaults":376,"../../lib":503,"../heatmap/xyz_defaults":806,"./attributes":807}],810:[function(_dereq_,module,exports){
'use strict';
var deprecationWarning = [
'*heatmapgl* trace is deprecated!',
'Please consider switching to the *heatmap* or *image* trace types.',
'Alternatively you could contribute/sponsor rewriting this trace type',
'based on cartesian features and using regl framework.'
].join(' ');
module.exports = {
attributes: _dereq_('./attributes'),
supplyDefaults: _dereq_('./defaults'),
colorbar: _dereq_('../heatmap/colorbar'),
calc: _dereq_('../heatmap/calc'),
plot: _dereq_('./convert'),
moduleType: 'trace',
name: 'heatmapgl',
basePlotModule: _dereq_('../../plots/gl2d'),
categories: ['gl', 'gl2d', '2dMap'],
meta: {
}
};
},{"../../plots/gl2d":596,"../heatmap/calc":793,"../heatmap/colorbar":795,"./attributes":807,"./convert":808,"./defaults":809}],811:[function(_dereq_,module,exports){
'use strict';
var barAttrs = _dereq_('../bar/attributes');
var axisHoverFormat = _dereq_('../../plots/cartesian/axis_format_attributes').axisHoverFormat;
var hovertemplateAttrs = _dereq_('../../plots/template_attributes').hovertemplateAttrs;
var makeBinAttrs = _dereq_('./bin_attributes');
var constants = _dereq_('./constants');
var extendFlat = _dereq_('../../lib/extend').extendFlat;
module.exports = {
x: {
valType: 'data_array',
editType: 'calc+clearAxisTypes',
},
y: {
valType: 'data_array',
editType: 'calc+clearAxisTypes',
},
xhoverformat: axisHoverFormat('x'),
yhoverformat: axisHoverFormat('y'),
text: extendFlat({}, barAttrs.text, {
}),
hovertext: extendFlat({}, barAttrs.hovertext, {
}),
orientation: barAttrs.orientation,
histfunc: {
valType: 'enumerated',
values: ['count', 'sum', 'avg', 'min', 'max'],
dflt: 'count',
editType: 'calc',
},
histnorm: {
valType: 'enumerated',
values: ['', 'percent', 'probability', 'density', 'probability density'],
dflt: '',
editType: 'calc',
},
cumulative: {
enabled: {
valType: 'boolean',
dflt: false,
editType: 'calc',
},
direction: {
valType: 'enumerated',
values: ['increasing', 'decreasing'],
dflt: 'increasing',
editType: 'calc',
},
currentbin: {
valType: 'enumerated',
values: ['include', 'exclude', 'half'],
dflt: 'include',
editType: 'calc',
},
editType: 'calc'
},
nbinsx: {
valType: 'integer',
min: 0,
dflt: 0,
editType: 'calc',
},
xbins: makeBinAttrs('x', true),
nbinsy: {
valType: 'integer',
min: 0,
dflt: 0,
editType: 'calc',
},
ybins: makeBinAttrs('y', true),
autobinx: {
valType: 'boolean',
dflt: null,
editType: 'calc',
},
autobiny: {
valType: 'boolean',
dflt: null,
editType: 'calc',
},
bingroup: {
valType: 'string',
dflt: '',
editType: 'calc',
},
hovertemplate: hovertemplateAttrs({}, {
keys: constants.eventDataKeys
}),
marker: barAttrs.marker,
offsetgroup: barAttrs.offsetgroup,
alignmentgroup: barAttrs.alignmentgroup,
selected: barAttrs.selected,
unselected: barAttrs.unselected,
_deprecated: {
bardir: barAttrs._deprecated.bardir
}
};
},{"../../lib/extend":493,"../../plots/cartesian/axis_format_attributes":557,"../../plots/template_attributes":633,"../bar/attributes":648,"./bin_attributes":813,"./constants":817}],812:[function(_dereq_,module,exports){
'use strict';
module.exports = function doAvg(size, counts) {
var nMax = size.length;
var total = 0;
for(var i = 0; i < nMax; i++) {
if(counts[i]) {
size[i] /= counts[i];
total += size[i];
} else size[i] = null;
}
return total;
};
},{}],813:[function(_dereq_,module,exports){
'use strict';
module.exports = function makeBinAttrs(axLetter, match) {
return {
start: {
valType: 'any', // for date axes
editType: 'calc',
},
end: {
valType: 'any', // for date axes
editType: 'calc',
},
size: {
valType: 'any', // for date axes
editType: 'calc',
},
editType: 'calc'
};
};
},{}],814:[function(_dereq_,module,exports){
'use strict';
var isNumeric = _dereq_('fast-isnumeric');
module.exports = {
count: function(n, i, size) {
size[n]++;
return 1;
},
sum: function(n, i, size, counterData) {
var v = counterData[i];
if(isNumeric(v)) {
v = Number(v);
size[n] += v;
return v;
}
return 0;
},
avg: function(n, i, size, counterData, counts) {
var v = counterData[i];
if(isNumeric(v)) {
v = Number(v);
size[n] += v;
counts[n]++;
}
return 0;
},
min: function(n, i, size, counterData) {
var v = counterData[i];
if(isNumeric(v)) {
v = Number(v);
if(!isNumeric(size[n])) {
size[n] = v;
return v;
} else if(size[n] > v) {
var delta = v - size[n];
size[n] = v;
return delta;
}
}
return 0;
},
max: function(n, i, size, counterData) {
var v = counterData[i];
if(isNumeric(v)) {
v = Number(v);
if(!isNumeric(size[n])) {
size[n] = v;
return v;
} else if(size[n] < v) {
var delta = v - size[n];
size[n] = v;
return delta;
}
}
return 0;
}
};
},{"fast-isnumeric":190}],815:[function(_dereq_,module,exports){
'use strict';
var numConstants = _dereq_('../../constants/numerical');
var oneYear = numConstants.ONEAVGYEAR;
var oneMonth = numConstants.ONEAVGMONTH;
var oneDay = numConstants.ONEDAY;
var oneHour = numConstants.ONEHOUR;
var oneMin = numConstants.ONEMIN;
var oneSec = numConstants.ONESEC;
var tickIncrement = _dereq_('../../plots/cartesian/axes').tickIncrement;
/*
* make a function that will find rounded bin edges
* @param {number} leftGap: how far from the left edge of any bin is the closest data value?
* @param {number} rightGap: how far from the right edge of any bin is the closest data value?
* @param {Array[number]} binEdges: the actual edge values used in binning
* @param {object} pa: the position axis
* @param {string} calendar: the data calendar
*
* @return {function(v, isRightEdge)}:
* find the start (isRightEdge is falsy) or end (truthy) label value for a bin edge `v`
*/
module.exports = function getBinSpanLabelRound(leftGap, rightGap, binEdges, pa, calendar) {
// the rounding digit is the largest digit that changes in *all* of 4 regions:
// - inside the rightGap before binEdges[0] (shifted 10% to the left)
// - inside the leftGap after binEdges[0] (expanded by 10% of rightGap on each end)
// - same for binEdges[1]
var dv0 = -1.1 * rightGap;
var dv1 = -0.1 * rightGap;
var dv2 = leftGap - dv1;
var edge0 = binEdges[0];
var edge1 = binEdges[1];
var leftDigit = Math.min(
biggestDigitChanged(edge0 + dv1, edge0 + dv2, pa, calendar),
biggestDigitChanged(edge1 + dv1, edge1 + dv2, pa, calendar)
);
var rightDigit = Math.min(
biggestDigitChanged(edge0 + dv0, edge0 + dv1, pa, calendar),
biggestDigitChanged(edge1 + dv0, edge1 + dv1, pa, calendar)
);
// normally we try to make the label for the right edge different from
// the left edge label, so it's unambiguous which bin gets data on the edge.
// but if this results in more than 3 extra digits (or for dates, more than
// 2 fields ie hr&min or min&sec, which is 3600x), it'll be more clutter than
// useful so keep the label cleaner instead
var digit, disambiguateEdges;
if(leftDigit > rightDigit && rightDigit < Math.abs(edge1 - edge0) / 4000) {
digit = leftDigit;
disambiguateEdges = false;
} else {
digit = Math.min(leftDigit, rightDigit);
disambiguateEdges = true;
}
if(pa.type === 'date' && digit > oneDay) {
var dashExclude = (digit === oneYear) ? 1 : 6;
var increment = (digit === oneYear) ? 'M12' : 'M1';
return function(v, isRightEdge) {
var dateStr = pa.c2d(v, oneYear, calendar);
var dashPos = dateStr.indexOf('-', dashExclude);
if(dashPos > 0) dateStr = dateStr.substr(0, dashPos);
var roundedV = pa.d2c(dateStr, 0, calendar);
if(roundedV < v) {
var nextV = tickIncrement(roundedV, increment, false, calendar);
if((roundedV + nextV) / 2 < v + leftGap) roundedV = nextV;
}
if(isRightEdge && disambiguateEdges) {
return tickIncrement(roundedV, increment, true, calendar);
}
return roundedV;
};
}
return function(v, isRightEdge) {
var roundedV = digit * Math.round(v / digit);
// if we rounded down and we could round up and still be < leftGap
// (or what leftGap values round to), do that
if(roundedV + (digit / 10) < v && roundedV + (digit * 0.9) < v + leftGap) {
roundedV += digit;
}
// finally for the right edge back off one digit - but only if we can do that
// and not clip off any data that's potentially in the bin
if(isRightEdge && disambiguateEdges) {
roundedV -= digit;
}
return roundedV;
};
};
/*
* Find the largest digit that changes within a (calcdata) region [v1, v2]
* if dates, "digit" means date/time part when it's bigger than a second
* returns the unit value to round to this digit, eg 0.01 to round to hundredths, or
* 100 to round to hundreds. returns oneMonth or oneYear for month or year rounding,
* so that Math.min will work, rather than 'M1' and 'M12'
*/
function biggestDigitChanged(v1, v2, pa, calendar) {
// are we crossing zero? can't say anything.
// in principle this doesn't apply to dates but turns out this doesn't matter.
if(v1 * v2 <= 0) return Infinity;
var dv = Math.abs(v2 - v1);
var isDate = pa.type === 'date';
var digit = biggestGuaranteedDigitChanged(dv, isDate);
// see if a larger digit also changed
for(var i = 0; i < 10; i++) {
// numbers: next digit needs to be >10x but <100x then gets rounded down.
// dates: next digit can be as much as 60x (then rounded down)
var nextDigit = biggestGuaranteedDigitChanged(digit * 80, isDate);
// if we get to years, the chain stops
if(digit === nextDigit) break;
if(didDigitChange(nextDigit, v1, v2, isDate, pa, calendar)) digit = nextDigit;
else break;
}
return digit;
}
/*
* Find the largest digit that *definitely* changes in a region [v, v + dv] for any v
* for nonuniform date regions (months/years) pick the largest
*/
function biggestGuaranteedDigitChanged(dv, isDate) {
if(isDate && dv > oneSec) {
// this is supposed to be the biggest *guaranteed* change
// so compare to the longest month and year across any calendar,
// and we'll iterate back up later
// note: does not support rounding larger than one year. We could add
// that if anyone wants it, but seems unusual and not strictly necessary.
if(dv > oneDay) {
if(dv > oneYear * 1.1) return oneYear;
if(dv > oneMonth * 1.1) return oneMonth;
return oneDay;
}
if(dv > oneHour) return oneHour;
if(dv > oneMin) return oneMin;
return oneSec;
}
return Math.pow(10, Math.floor(Math.log(dv) / Math.LN10));
}
function didDigitChange(digit, v1, v2, isDate, pa, calendar) {
if(isDate && digit > oneDay) {
var dateParts1 = dateParts(v1, pa, calendar);
var dateParts2 = dateParts(v2, pa, calendar);
var parti = (digit === oneYear) ? 0 : 1;
return dateParts1[parti] !== dateParts2[parti];
}
return Math.floor(v2 / digit) - Math.floor(v1 / digit) > 0.1;
}
function dateParts(v, pa, calendar) {
var parts = pa.c2d(v, oneYear, calendar).split('-');
if(parts[0] === '') {
parts.unshift();
parts[0] = '-' + parts[0];
}
return parts;
}
},{"../../constants/numerical":479,"../../plots/cartesian/axes":554}],816:[function(_dereq_,module,exports){
'use strict';
var isNumeric = _dereq_('fast-isnumeric');
var Lib = _dereq_('../../lib');
var Registry = _dereq_('../../registry');
var Axes = _dereq_('../../plots/cartesian/axes');
var arraysToCalcdata = _dereq_('../bar/arrays_to_calcdata');
var binFunctions = _dereq_('./bin_functions');
var normFunctions = _dereq_('./norm_functions');
var doAvg = _dereq_('./average');
var getBinSpanLabelRound = _dereq_('./bin_label_vals');
function calc(gd, trace) {
var pos = [];
var size = [];
var pa = Axes.getFromId(gd, trace.orientation === 'h' ? trace.yaxis : trace.xaxis);
var mainData = trace.orientation === 'h' ? 'y' : 'x';
var counterData = {x: 'y', y: 'x'}[mainData];
var calendar = trace[mainData + 'calendar'];
var cumulativeSpec = trace.cumulative;
var i;
var binsAndPos = calcAllAutoBins(gd, trace, pa, mainData);
var binSpec = binsAndPos[0];
var pos0 = binsAndPos[1];
var nonuniformBins = typeof binSpec.size === 'string';
var binEdges = [];
var bins = nonuniformBins ? binEdges : binSpec;
// make the empty bin array
var inc = [];
var counts = [];
var inputPoints = [];
var total = 0;
var norm = trace.histnorm;
var func = trace.histfunc;
var densityNorm = norm.indexOf('density') !== -1;
var i2, binEnd, n;
if(cumulativeSpec.enabled && densityNorm) {
// we treat "cumulative" like it means "integral" if you use a density norm,
// which in the end means it's the same as without "density"
norm = norm.replace(/ ?density$/, '');
densityNorm = false;
}
var extremeFunc = func === 'max' || func === 'min';
var sizeInit = extremeFunc ? null : 0;
var binFunc = binFunctions.count;
var normFunc = normFunctions[norm];
var isAvg = false;
var pr2c = function(v) { return pa.r2c(v, 0, calendar); };
var rawCounterData;
if(Lib.isArrayOrTypedArray(trace[counterData]) && func !== 'count') {
rawCounterData = trace[counterData];
isAvg = func === 'avg';
binFunc = binFunctions[func];
}
// create the bins (and any extra arrays needed)
// assume more than 1e6 bins is an error, so we don't crash the browser
i = pr2c(binSpec.start);
// decrease end a little in case of rounding errors
binEnd = pr2c(binSpec.end) + (i - Axes.tickIncrement(i, binSpec.size, false, calendar)) / 1e6;
while(i < binEnd && pos.length < 1e6) {
i2 = Axes.tickIncrement(i, binSpec.size, false, calendar);
pos.push((i + i2) / 2);
size.push(sizeInit);
inputPoints.push([]);
// nonuniform bins (like months) we need to search,
// rather than straight calculate the bin we're in
binEdges.push(i);
// nonuniform bins also need nonuniform normalization factors
if(densityNorm) inc.push(1 / (i2 - i));
if(isAvg) counts.push(0);
// break to avoid infinite loops
if(i2 <= i) break;
i = i2;
}
binEdges.push(i);
// for date axes we need bin bounds to be calcdata. For nonuniform bins
// we already have this, but uniform with start/end/size they're still strings.
if(!nonuniformBins && pa.type === 'date') {
bins = {
start: pr2c(bins.start),
end: pr2c(bins.end),
size: bins.size
};
}
// stash left and right gaps by group
if(!gd._fullLayout._roundFnOpts) gd._fullLayout._roundFnOpts = {};
var groupName = trace['_' + mainData + 'bingroup'];
var roundFnOpts = {leftGap: Infinity, rightGap: Infinity};
if(groupName) {
if(!gd._fullLayout._roundFnOpts[groupName]) gd._fullLayout._roundFnOpts[groupName] = roundFnOpts;
roundFnOpts = gd._fullLayout._roundFnOpts[groupName];
}
// bin the data
// and make histogram-specific pt-number-to-cd-index map object
var nMax = size.length;
var uniqueValsPerBin = true;
var leftGap = roundFnOpts.leftGap;
var rightGap = roundFnOpts.rightGap;
var ptNumber2cdIndex = {};
for(i = 0; i < pos0.length; i++) {
var posi = pos0[i];
n = Lib.findBin(posi, bins);
if(n >= 0 && n < nMax) {
total += binFunc(n, i, size, rawCounterData, counts);
if(uniqueValsPerBin && inputPoints[n].length && posi !== pos0[inputPoints[n][0]]) {
uniqueValsPerBin = false;
}
inputPoints[n].push(i);
ptNumber2cdIndex[i] = n;
leftGap = Math.min(leftGap, posi - binEdges[n]);
rightGap = Math.min(rightGap, binEdges[n + 1] - posi);
}
}
roundFnOpts.leftGap = leftGap;
roundFnOpts.rightGap = rightGap;
var roundFn;
if(!uniqueValsPerBin) {
roundFn = function(v, isRightEdge) {
return function() {
var roundFnOpts = gd._fullLayout._roundFnOpts[groupName];
return getBinSpanLabelRound(
roundFnOpts.leftGap,
roundFnOpts.rightGap,
binEdges, pa, calendar
)(v, isRightEdge);
};
};
}
// average and/or normalize the data, if needed
if(isAvg) total = doAvg(size, counts);
if(normFunc) normFunc(size, total, inc);
// after all normalization etc, now we can accumulate if desired
if(cumulativeSpec.enabled) cdf(size, cumulativeSpec.direction, cumulativeSpec.currentbin);
var seriesLen = Math.min(pos.length, size.length);
var cd = [];
var firstNonzero = 0;
var lastNonzero = seriesLen - 1;
// look for empty bins at the ends to remove, so autoscale omits them
for(i = 0; i < seriesLen; i++) {
if(size[i]) {
firstNonzero = i;
break;
}
}
for(i = seriesLen - 1; i >= firstNonzero; i--) {
if(size[i]) {
lastNonzero = i;
break;
}
}
// create the "calculated data" to plot
for(i = firstNonzero; i <= lastNonzero; i++) {
if((isNumeric(pos[i]) && isNumeric(size[i]))) {
var cdi = {
p: pos[i],
s: size[i],
b: 0
};
// setup hover and event data fields,
// N.B. pts and "hover" positions ph0/ph1 don't seem to make much sense
// for cumulative distributions
if(!cumulativeSpec.enabled) {
cdi.pts = inputPoints[i];
if(uniqueValsPerBin) {
cdi.ph0 = cdi.ph1 = (inputPoints[i].length) ? pos0[inputPoints[i][0]] : pos[i];
} else {
// Defer evaluation of ph(0|1) in crossTraceCalc
trace._computePh = true;
cdi.ph0 = roundFn(binEdges[i]);
cdi.ph1 = roundFn(binEdges[i + 1], true);
}
}
cd.push(cdi);
}
}
if(cd.length === 1) {
// when we collapse to a single bin, calcdata no longer describes bin size
// so we need to explicitly specify it
cd[0].width1 = Axes.tickIncrement(cd[0].p, binSpec.size, false, calendar) - cd[0].p;
}
arraysToCalcdata(cd, trace);
if(Lib.isArrayOrTypedArray(trace.selectedpoints)) {
Lib.tagSelected(cd, trace, ptNumber2cdIndex);
}
return cd;
}
/*
* calcAllAutoBins: we want all histograms inside the same bingroup
* (see logic in Histogram.crossTraceDefaults) to share bin specs
*
* If the user has explicitly specified differing
* bin specs, there's nothing we can do, but if possible we will try to use the
* smallest bins of any of the auto values for all histograms inside the same
* bingroup.
*/
function calcAllAutoBins(gd, trace, pa, mainData, _overlayEdgeCase) {
var binAttr = mainData + 'bins';
var fullLayout = gd._fullLayout;
var groupName = trace['_' + mainData + 'bingroup'];
var binOpts = fullLayout._histogramBinOpts[groupName];
var isOverlay = fullLayout.barmode === 'overlay';
var i, traces, tracei, calendar, pos0, autoVals, cumulativeSpec;
var r2c = function(v) { return pa.r2c(v, 0, calendar); };
var c2r = function(v) { return pa.c2r(v, 0, calendar); };
var cleanBound = pa.type === 'date' ?
function(v) { return (v || v === 0) ? Lib.cleanDate(v, null, calendar) : null; } :
function(v) { return isNumeric(v) ? Number(v) : null; };
function setBound(attr, bins, newBins) {
if(bins[attr + 'Found']) {
bins[attr] = cleanBound(bins[attr]);
if(bins[attr] === null) bins[attr] = newBins[attr];
} else {
autoVals[attr] = bins[attr] = newBins[attr];
Lib.nestedProperty(traces[0], binAttr + '.' + attr).set(newBins[attr]);
}
}
// all but the first trace in this group has already been marked finished
// clear this flag, so next time we run calc we will run autobin again
if(trace['_' + mainData + 'autoBinFinished']) {
delete trace['_' + mainData + 'autoBinFinished'];
} else {
traces = binOpts.traces;
var allPos = [];
// Note: we're including `legendonly` traces here for autobin purposes,
// so that showing & hiding from the legend won't affect bins.
// But this complicates things a bit since those traces don't `calc`,
// hence `isFirstVisible`.
var isFirstVisible = true;
var has2dMap = false;
var hasHist2dContour = false;
for(i = 0; i < traces.length; i++) {
tracei = traces[i];
if(tracei.visible) {
var mainDatai = binOpts.dirs[i];
pos0 = tracei['_' + mainDatai + 'pos0'] = pa.makeCalcdata(tracei, mainDatai);
allPos = Lib.concat(allPos, pos0);
delete tracei['_' + mainData + 'autoBinFinished'];
if(trace.visible === true) {
if(isFirstVisible) {
isFirstVisible = false;
} else {
delete tracei._autoBin;
tracei['_' + mainData + 'autoBinFinished'] = 1;
}
if(Registry.traceIs(tracei, '2dMap')) {
has2dMap = true;
}
if(tracei.type === 'histogram2dcontour') {
hasHist2dContour = true;
}
}
}
}
calendar = traces[0][mainData + 'calendar'];
var newBinSpec = Axes.autoBin(allPos, pa, binOpts.nbins, has2dMap, calendar, binOpts.sizeFound && binOpts.size);
var autoBin = traces[0]._autoBin = {};
autoVals = autoBin[binOpts.dirs[0]] = {};
if(hasHist2dContour) {
// the "true" 2nd argument reverses the tick direction (which we can't
// just do with a minus sign because of month bins)
if(!binOpts.size) {
newBinSpec.start = c2r(Axes.tickIncrement(
r2c(newBinSpec.start), newBinSpec.size, true, calendar));
}
if(binOpts.end === undefined) {
newBinSpec.end = c2r(Axes.tickIncrement(
r2c(newBinSpec.end), newBinSpec.size, false, calendar));
}
}
// Edge case: single-valued histogram overlaying others
// Use them all together to calculate the bin size for the single-valued one
if(isOverlay && !Registry.traceIs(trace, '2dMap') && newBinSpec._dataSpan === 0 &&
pa.type !== 'category' && pa.type !== 'multicategory') {
// Several single-valued histograms! Stop infinite recursion,
// just return an extra flag that tells handleSingleValueOverlays
// to sort out this trace too
if(_overlayEdgeCase) return [newBinSpec, pos0, true];
newBinSpec = handleSingleValueOverlays(gd, trace, pa, mainData, binAttr);
}
// adjust for CDF edge cases
cumulativeSpec = tracei.cumulative || {};
if(cumulativeSpec.enabled && (cumulativeSpec.currentbin !== 'include')) {
if(cumulativeSpec.direction === 'decreasing') {
newBinSpec.start = c2r(Axes.tickIncrement(
r2c(newBinSpec.start), newBinSpec.size, true, calendar));
} else {
newBinSpec.end = c2r(Axes.tickIncrement(
r2c(newBinSpec.end), newBinSpec.size, false, calendar));
}
}
binOpts.size = newBinSpec.size;
if(!binOpts.sizeFound) {
autoVals.size = newBinSpec.size;
Lib.nestedProperty(traces[0], binAttr + '.size').set(newBinSpec.size);
}
setBound('start', binOpts, newBinSpec);
setBound('end', binOpts, newBinSpec);
}
pos0 = trace['_' + mainData + 'pos0'];
delete trace['_' + mainData + 'pos0'];
// Each trace can specify its own start/end, or if omitted
// we ensure they're beyond the bounds of this trace's data,
// and we need to make sure start is aligned with the main start
var traceInputBins = trace._input[binAttr] || {};
var traceBinOptsCalc = Lib.extendFlat({}, binOpts);
var mainStart = binOpts.start;
var startIn = pa.r2l(traceInputBins.start);
var hasStart = startIn !== undefined;
if((binOpts.startFound || hasStart) && startIn !== pa.r2l(mainStart)) {
// We have an explicit start to reconcile across traces
// if this trace has an explicit start, shift it down to a bin edge
// if another trace had an explicit start, shift it down to a
// bin edge past our data
var traceStart = hasStart ?
startIn :
Lib.aggNums(Math.min, null, pos0);
var dummyAx = {
type: (pa.type === 'category' || pa.type === 'multicategory') ? 'linear' : pa.type,
r2l: pa.r2l,
dtick: binOpts.size,
tick0: mainStart,
calendar: calendar,
range: ([traceStart, Axes.tickIncrement(traceStart, binOpts.size, false, calendar)]).map(pa.l2r)
};
var newStart = Axes.tickFirst(dummyAx);
if(newStart > pa.r2l(traceStart)) {
newStart = Axes.tickIncrement(newStart, binOpts.size, true, calendar);
}
traceBinOptsCalc.start = pa.l2r(newStart);
if(!hasStart) Lib.nestedProperty(trace, binAttr + '.start').set(traceBinOptsCalc.start);
}
var mainEnd = binOpts.end;
var endIn = pa.r2l(traceInputBins.end);
var hasEnd = endIn !== undefined;
if((binOpts.endFound || hasEnd) && endIn !== pa.r2l(mainEnd)) {
// Reconciling an explicit end is easier, as it doesn't need to
// match bin edges
var traceEnd = hasEnd ?
endIn :
Lib.aggNums(Math.max, null, pos0);
traceBinOptsCalc.end = pa.l2r(traceEnd);
if(!hasEnd) Lib.nestedProperty(trace, binAttr + '.start').set(traceBinOptsCalc.end);
}
// Backward compatibility for one-time autobinning.
// autobin: true is handled in cleanData, but autobin: false
// needs to be here where we have determined the values.
var autoBinAttr = 'autobin' + mainData;
if(trace._input[autoBinAttr] === false) {
trace._input[binAttr] = Lib.extendFlat({}, trace[binAttr] || {});
delete trace._input[autoBinAttr];
delete trace[autoBinAttr];
}
return [traceBinOptsCalc, pos0];
}
/*
* Adjust single-value histograms in overlay mode to make as good a
* guess as we can at autobin values the user would like.
*
* Returns the binSpec for the trace that sparked all this
*/
function handleSingleValueOverlays(gd, trace, pa, mainData, binAttr) {
var fullLayout = gd._fullLayout;
var overlaidTraceGroup = getConnectedHistograms(gd, trace);
var pastThisTrace = false;
var minSize = Infinity;
var singleValuedTraces = [trace];
var i, tracei, binOpts;
// first collect all the:
// - min bin size from all multi-valued traces
// - single-valued traces
for(i = 0; i < overlaidTraceGroup.length; i++) {
tracei = overlaidTraceGroup[i];
if(tracei === trace) {
pastThisTrace = true;
} else if(!pastThisTrace) {
// This trace has already had its autobins calculated, so either:
// - it is part of a bingroup
// - it is NOT a single-valued trace
binOpts = fullLayout._histogramBinOpts[tracei['_' + mainData + 'bingroup']];
minSize = Math.min(minSize, binOpts.size || tracei[binAttr].size);
} else {
var resulti = calcAllAutoBins(gd, tracei, pa, mainData, true);
var binSpeci = resulti[0];
var isSingleValued = resulti[2];
// so we can use this result when we get to tracei in the normal
// course of events, mark it as done and put _pos0 back
tracei['_' + mainData + 'autoBinFinished'] = 1;
tracei['_' + mainData + 'pos0'] = resulti[1];
if(isSingleValued) {
singleValuedTraces.push(tracei);
} else {
minSize = Math.min(minSize, binSpeci.size);
}
}
}
// find the real data values for each single-valued trace
// hunt through pos0 for the first valid value
var dataVals = new Array(singleValuedTraces.length);
for(i = 0; i < singleValuedTraces.length; i++) {
var pos0 = singleValuedTraces[i]['_' + mainData + 'pos0'];
for(var j = 0; j < pos0.length; j++) {
if(pos0[j] !== undefined) {
dataVals[i] = pos0[j];
break;
}
}
}
// are ALL traces are single-valued? use the min difference between
// all of their values (which defaults to 1 if there's still only one)
if(!isFinite(minSize)) {
minSize = Lib.distinctVals(dataVals).minDiff;
}
// now apply the min size we found to all single-valued traces
for(i = 0; i < singleValuedTraces.length; i++) {
tracei = singleValuedTraces[i];
var calendar = tracei[mainData + 'calendar'];
var newBins = {
start: pa.c2r(dataVals[i] - minSize / 2, 0, calendar),
end: pa.c2r(dataVals[i] + minSize / 2, 0, calendar),
size: minSize
};
tracei._input[binAttr] = tracei[binAttr] = newBins;
binOpts = fullLayout._histogramBinOpts[tracei['_' + mainData + 'bingroup']];
if(binOpts) Lib.extendFlat(binOpts, newBins);
}
return trace[binAttr];
}
/*
* Return an array of histograms that share axes and orientation.
*
* Only considers histograms. In principle we could include bars in a
* similar way to how we do manually binned histograms, though this
* would have tons of edge cases and value judgments to make.
*/
function getConnectedHistograms(gd, trace) {
var xid = trace.xaxis;
var yid = trace.yaxis;
var orientation = trace.orientation;
var out = [];
var fullData = gd._fullData;
for(var i = 0; i < fullData.length; i++) {
var tracei = fullData[i];
if(tracei.type === 'histogram' &&
tracei.visible === true &&
tracei.orientation === orientation &&
tracei.xaxis === xid && tracei.yaxis === yid
) {
out.push(tracei);
}
}
return out;
}
function cdf(size, direction, currentBin) {
var i, vi, prevSum;
function firstHalfPoint(i) {
prevSum = size[i];
size[i] /= 2;
}
function nextHalfPoint(i) {
vi = size[i];
size[i] = prevSum + vi / 2;
prevSum += vi;
}
if(currentBin === 'half') {
if(direction === 'increasing') {
firstHalfPoint(0);
for(i = 1; i < size.length; i++) {
nextHalfPoint(i);
}
} else {
firstHalfPoint(size.length - 1);
for(i = size.length - 2; i >= 0; i--) {
nextHalfPoint(i);
}
}
} else if(direction === 'increasing') {
for(i = 1; i < size.length; i++) {
size[i] += size[i - 1];
}
// 'exclude' is identical to 'include' just shifted one bin over
if(currentBin === 'exclude') {
size.unshift(0);
size.pop();
}
} else {
for(i = size.length - 2; i >= 0; i--) {
size[i] += size[i + 1];
}
if(currentBin === 'exclude') {
size.push(0);
size.shift();
}
}
}
module.exports = {
calc: calc,
calcAllAutoBins: calcAllAutoBins
};
},{"../../lib":503,"../../plots/cartesian/axes":554,"../../registry":638,"../bar/arrays_to_calcdata":647,"./average":812,"./bin_functions":814,"./bin_label_vals":815,"./norm_functions":823,"fast-isnumeric":190}],817:[function(_dereq_,module,exports){
'use strict';
module.exports = {
eventDataKeys: ['binNumber']
};
},{}],818:[function(_dereq_,module,exports){
'use strict';
var Lib = _dereq_('../../lib');
var axisIds = _dereq_('../../plots/cartesian/axis_ids');
var traceIs = _dereq_('../../registry').traceIs;
var handleGroupingDefaults = _dereq_('../bar/defaults').handleGroupingDefaults;
var nestedProperty = Lib.nestedProperty;
var getAxisGroup = _dereq_('../../plots/cartesian/constraints').getAxisGroup;
var BINATTRS = [
{aStr: {x: 'xbins.start', y: 'ybins.start'}, name: 'start'},
{aStr: {x: 'xbins.end', y: 'ybins.end'}, name: 'end'},
{aStr: {x: 'xbins.size', y: 'ybins.size'}, name: 'size'},
{aStr: {x: 'nbinsx', y: 'nbinsy'}, name: 'nbins'}
];
var BINDIRECTIONS = ['x', 'y'];
// handle bin attrs and relink auto-determined values so fullData is complete
module.exports = function crossTraceDefaults(fullData, fullLayout) {
var allBinOpts = fullLayout._histogramBinOpts = {};
var histTraces = [];
var mustMatchTracesLookup = {};
var otherTracesList = [];
var traceOut, traces, groupName, binDir;
var i, j, k;
function coerce(attr, dflt) {
return Lib.coerce(traceOut._input, traceOut, traceOut._module.attributes, attr, dflt);
}
function orientation2binDir(traceOut) {
return traceOut.orientation === 'v' ? 'x' : 'y';
}
function getAxisType(traceOut, binDir) {
var ax = axisIds.getFromTrace({_fullLayout: fullLayout}, traceOut, binDir);
return ax.type;
}
function fillBinOpts(traceOut, groupName, binDir) {
// N.B. group traces that don't have a bingroup with themselves
var fallbackGroupName = traceOut.uid + '__' + binDir;
if(!groupName) groupName = fallbackGroupName;
var axType = getAxisType(traceOut, binDir);
var calendar = traceOut[binDir + 'calendar'] || '';
var binOpts = allBinOpts[groupName];
var needsNewItem = true;
if(binOpts) {
if(axType === binOpts.axType && calendar === binOpts.calendar) {
needsNewItem = false;
binOpts.traces.push(traceOut);
binOpts.dirs.push(binDir);
} else {
groupName = fallbackGroupName;
if(axType !== binOpts.axType) {
Lib.warn([
'Attempted to group the bins of trace', traceOut.index,
'set on a', 'type:' + axType, 'axis',
'with bins on', 'type:' + binOpts.axType, 'axis.'
].join(' '));
}
if(calendar !== binOpts.calendar) {
// prohibit bingroup for traces using different calendar,
// there's probably a way to make this work, but skip for now
Lib.warn([
'Attempted to group the bins of trace', traceOut.index,
'set with a', calendar, 'calendar',
'with bins',
(binOpts.calendar ? 'on a ' + binOpts.calendar + ' calendar' : 'w/o a set calendar')
].join(' '));
}
}
}
if(needsNewItem) {
allBinOpts[groupName] = {
traces: [traceOut],
dirs: [binDir],
axType: axType,
calendar: traceOut[binDir + 'calendar'] || ''
};
}
traceOut['_' + binDir + 'bingroup'] = groupName;
}
for(i = 0; i < fullData.length; i++) {
traceOut = fullData[i];
if(traceIs(traceOut, 'histogram')) {
histTraces.push(traceOut);
// TODO: this shouldn't be relinked as it's only used within calc
// https://github.com/plotly/plotly.js/issues/749
delete traceOut._xautoBinFinished;
delete traceOut._yautoBinFinished;
// N.B. need to coerce *alignmentgroup* before *bingroup*, as traces
// in same alignmentgroup "have to match"
if(!traceIs(traceOut, '2dMap')) {
handleGroupingDefaults(traceOut._input, traceOut, fullLayout, coerce);
}
}
}
var alignmentOpts = fullLayout._alignmentOpts || {};
// Look for traces that "have to match", that is:
// - 1d histogram traces on the same subplot with same orientation under barmode:stack,
// - 1d histogram traces on the same subplot with same orientation under barmode:group
// - 1d histogram traces on the same position axis with the same orientation
// and the same *alignmentgroup* (coerced under barmode:group)
// - Once `stackgroup` gets implemented (see https://github.com/plotly/plotly.js/issues/3614),
// traces within the same stackgroup will also "have to match"
for(i = 0; i < histTraces.length; i++) {
traceOut = histTraces[i];
groupName = '';
if(!traceIs(traceOut, '2dMap')) {
binDir = orientation2binDir(traceOut);
if(fullLayout.barmode === 'group' && traceOut.alignmentgroup) {
var pa = traceOut[binDir + 'axis'];
var aGroupId = getAxisGroup(fullLayout, pa) + traceOut.orientation;
if((alignmentOpts[aGroupId] || {})[traceOut.alignmentgroup]) {
groupName = aGroupId;
}
}
if(!groupName && fullLayout.barmode !== 'overlay') {
groupName = (
getAxisGroup(fullLayout, traceOut.xaxis) +
getAxisGroup(fullLayout, traceOut.yaxis) +
orientation2binDir(traceOut)
);
}
}
if(groupName) {
if(!mustMatchTracesLookup[groupName]) {
mustMatchTracesLookup[groupName] = [];
}
mustMatchTracesLookup[groupName].push(traceOut);
} else {
otherTracesList.push(traceOut);
}
}
// Setup binOpts for traces that have to match,
// if the traces have a valid bingroup, use that
// if not use axis+binDir groupName
for(groupName in mustMatchTracesLookup) {
traces = mustMatchTracesLookup[groupName];
// no need to 'force' anything when a single
// trace is detected as "must match"
if(traces.length === 1) {
otherTracesList.push(traces[0]);
continue;
}
var binGroupFound = false;
if(traces.length) {
traceOut = traces[0];
binGroupFound = coerce('bingroup');
}
groupName = binGroupFound || groupName;
for(i = 0; i < traces.length; i++) {
traceOut = traces[i];
var bingroupIn = traceOut._input.bingroup;
if(bingroupIn && bingroupIn !== groupName) {
Lib.warn([
'Trace', traceOut.index, 'must match',
'within bingroup', groupName + '.',
'Ignoring its bingroup:', bingroupIn, 'setting.'
].join(' '));
}
traceOut.bingroup = groupName;
// N.B. no need to worry about 2dMap case
// (where both bin direction are set in each trace)
// as 2dMap trace never "have to match"
fillBinOpts(traceOut, groupName, orientation2binDir(traceOut));
}
}
// setup binOpts for traces that can but don't have to match,
// notice that these traces can be matched with traces that have to match
for(i = 0; i < otherTracesList.length; i++) {
traceOut = otherTracesList[i];
var binGroup = coerce('bingroup');
if(traceIs(traceOut, '2dMap')) {
for(k = 0; k < 2; k++) {
binDir = BINDIRECTIONS[k];
var binGroupInDir = coerce(binDir + 'bingroup',
binGroup ? binGroup + '__' + binDir : null
);
fillBinOpts(traceOut, binGroupInDir, binDir);
}
} else {
fillBinOpts(traceOut, binGroup, orientation2binDir(traceOut));
}
}
// coerce bin attrs!
for(groupName in allBinOpts) {
var binOpts = allBinOpts[groupName];
traces = binOpts.traces;
for(j = 0; j < BINATTRS.length; j++) {
var attrSpec = BINATTRS[j];
var attr = attrSpec.name;
var aStr;
var autoVals;
// nbins(x|y) is moot if we have a size. This depends on
// nbins coming after size in binAttrs.
if(attr === 'nbins' && binOpts.sizeFound) continue;
for(i = 0; i < traces.length; i++) {
traceOut = traces[i];
binDir = binOpts.dirs[i];
aStr = attrSpec.aStr[binDir];
if(nestedProperty(traceOut._input, aStr).get() !== undefined) {
binOpts[attr] = coerce(aStr);
binOpts[attr + 'Found'] = true;
break;
}
autoVals = (traceOut._autoBin || {})[binDir] || {};
if(autoVals[attr]) {
// if this is the *first* autoval
nestedProperty(traceOut, aStr).set(autoVals[attr]);
}
}
// start and end we need to coerce anyway, after having collected the
// first of each into binOpts, in case a trace wants to restrict its
// data to a certain range
if(attr === 'start' || attr === 'end') {
for(; i < traces.length; i++) {
traceOut = traces[i];
if(traceOut['_' + binDir + 'bingroup']) {
autoVals = (traceOut._autoBin || {})[binDir] || {};
coerce(aStr, autoVals[attr]);
}
}
}
if(attr === 'nbins' && !binOpts.sizeFound && !binOpts.nbinsFound) {
traceOut = traces[0];
binOpts[attr] = coerce(aStr);
}
}
}
};
},{"../../lib":503,"../../plots/cartesian/axis_ids":558,"../../plots/cartesian/constraints":562,"../../registry":638,"../bar/defaults":652}],819:[function(_dereq_,module,exports){
'use strict';
var Registry = _dereq_('../../registry');
var Lib = _dereq_('../../lib');
var Color = _dereq_('../../components/color');
var handleStyleDefaults = _dereq_('../bar/style_defaults');
var attributes = _dereq_('./attributes');
module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout) {
function coerce(attr, dflt) {
return Lib.coerce(traceIn, traceOut, attributes, attr, dflt);
}
var x = coerce('x');
var y = coerce('y');
var cumulative = coerce('cumulative.enabled');
if(cumulative) {
coerce('cumulative.direction');
coerce('cumulative.currentbin');
}
coerce('text');
coerce('hovertext');
coerce('hovertemplate');
coerce('xhoverformat');
coerce('yhoverformat');
var orientation = coerce('orientation', (y && !x) ? 'h' : 'v');
var sampleLetter = orientation === 'v' ? 'x' : 'y';
var aggLetter = orientation === 'v' ? 'y' : 'x';
var len = (x && y) ?
Math.min(Lib.minRowLength(x) && Lib.minRowLength(y)) :
Lib.minRowLength(traceOut[sampleLetter] || []);
if(!len) {
traceOut.visible = false;
return;
}
traceOut._length = len;
var handleCalendarDefaults = Registry.getComponentMethod('calendars', 'handleTraceDefaults');
handleCalendarDefaults(traceIn, traceOut, ['x', 'y'], layout);
var hasAggregationData = traceOut[aggLetter];
if(hasAggregationData) coerce('histfunc');
coerce('histnorm');
// Note: bin defaults are now handled in Histogram.crossTraceDefaults
// autobin(x|y) are only included here to appease Plotly.validate
coerce('autobin' + sampleLetter);
handleStyleDefaults(traceIn, traceOut, coerce, defaultColor, layout);
Lib.coerceSelectionMarkerOpacity(traceOut, coerce);
var lineColor = (traceOut.marker.line || {}).color;
// override defaultColor for error bars with defaultLine
var errorBarsSupplyDefaults = Registry.getComponentMethod('errorbars', 'supplyDefaults');
errorBarsSupplyDefaults(traceIn, traceOut, lineColor || Color.defaultLine, {axis: 'y'});
errorBarsSupplyDefaults(traceIn, traceOut, lineColor || Color.defaultLine, {axis: 'x', inherit: 'y'});
};
},{"../../components/color":366,"../../lib":503,"../../registry":638,"../bar/style_defaults":663,"./attributes":811}],820:[function(_dereq_,module,exports){
'use strict';
module.exports = function eventData(out, pt, trace, cd, pointNumber) {
// standard cartesian event data
out.x = 'xVal' in pt ? pt.xVal : pt.x;
out.y = 'yVal' in pt ? pt.yVal : pt.y;
// for 2d histograms
if('zLabelVal' in pt) out.z = pt.zLabelVal;
if(pt.xa) out.xaxis = pt.xa;
if(pt.ya) out.yaxis = pt.ya;
// specific to histogram - CDFs do not have pts (yet?)
if(!(trace.cumulative || {}).enabled) {
var pts = Array.isArray(pointNumber) ?
cd[0].pts[pointNumber[0]][pointNumber[1]] :
cd[pointNumber].pts;
out.pointNumbers = pts;
out.binNumber = out.pointNumber;
delete out.pointNumber;
delete out.pointIndex;
var pointIndices;
if(trace._indexToPoints) {
pointIndices = [];
for(var i = 0; i < pts.length; i++) {
pointIndices = pointIndices.concat(trace._indexToPoints[pts[i]]);
}
} else {
pointIndices = pts;
}
out.pointIndices = pointIndices;
}
return out;
};
},{}],821:[function(_dereq_,module,exports){
'use strict';
var barHover = _dereq_('../bar/hover').hoverPoints;
var hoverLabelText = _dereq_('../../plots/cartesian/axes').hoverLabelText;
module.exports = function hoverPoints(pointData, xval, yval, hovermode, opts) {
var pts = barHover(pointData, xval, yval, hovermode, opts);
if(!pts) return;
pointData = pts[0];
var di = pointData.cd[pointData.index];
var trace = pointData.cd[0].trace;
if(!trace.cumulative.enabled) {
var posLetter = trace.orientation === 'h' ? 'y' : 'x';
pointData[posLetter + 'Label'] = hoverLabelText(pointData[posLetter + 'a'], [di.ph0, di.ph1], trace[posLetter + 'hoverformat']);
}
return pts;
};
},{"../../plots/cartesian/axes":554,"../bar/hover":655}],822:[function(_dereq_,module,exports){
'use strict';
/**
* Histogram has its own attribute, defaults and calc steps,
* but uses bar's plot to display
* and bar's crossTraceCalc (formerly known as setPositions) for stacking and grouping
*/
/**
* histogram errorBarsOK is debatable, but it's put in for backward compat.
* there are use cases for it - sqrt for a simple histogram works right now,
* constant and % work but they're not so meaningful. I guess it could be cool
* to allow quadrature combination of errors in summed histograms...
*/
module.exports = {
attributes: _dereq_('./attributes'),
layoutAttributes: _dereq_('../bar/layout_attributes'),
supplyDefaults: _dereq_('./defaults'),
crossTraceDefaults: _dereq_('./cross_trace_defaults'),
supplyLayoutDefaults: _dereq_('../bar/layout_defaults'),
calc: _dereq_('./calc').calc,
crossTraceCalc: _dereq_('../bar/cross_trace_calc').crossTraceCalc,
plot: _dereq_('../bar/plot').plot,
layerName: 'barlayer',
style: _dereq_('../bar/style').style,
styleOnSelect: _dereq_('../bar/style').styleOnSelect,
colorbar: _dereq_('../scatter/marker_colorbar'),
hoverPoints: _dereq_('./hover'),
selectPoints: _dereq_('../bar/select'),
eventData: _dereq_('./event_data'),
moduleType: 'trace',
name: 'histogram',
basePlotModule: _dereq_('../../plots/cartesian'),
categories: ['bar-like', 'cartesian', 'svg', 'bar', 'histogram', 'oriented', 'errorBarsOK', 'showLegend'],
meta: {
}
};
},{"../../plots/cartesian":568,"../bar/cross_trace_calc":651,"../bar/layout_attributes":657,"../bar/layout_defaults":658,"../bar/plot":659,"../bar/select":660,"../bar/style":662,"../scatter/marker_colorbar":943,"./attributes":811,"./calc":816,"./cross_trace_defaults":818,"./defaults":819,"./event_data":820,"./hover":821}],823:[function(_dereq_,module,exports){
'use strict';
module.exports = {
percent: function(size, total) {
var nMax = size.length;
var norm = 100 / total;
for(var n = 0; n < nMax; n++) size[n] *= norm;
},
probability: function(size, total) {
var nMax = size.length;
for(var n = 0; n < nMax; n++) size[n] /= total;
},
density: function(size, total, inc, yinc) {
var nMax = size.length;
yinc = yinc || 1;
for(var n = 0; n < nMax; n++) size[n] *= inc[n] * yinc;
},
'probability density': function(size, total, inc, yinc) {
var nMax = size.length;
if(yinc) total /= yinc;
for(var n = 0; n < nMax; n++) size[n] *= inc[n] / total;
}
};
},{}],824:[function(_dereq_,module,exports){
'use strict';
var histogramAttrs = _dereq_('../histogram/attributes');
var makeBinAttrs = _dereq_('../histogram/bin_attributes');
var heatmapAttrs = _dereq_('../heatmap/attributes');
var baseAttrs = _dereq_('../../plots/attributes');
var axisHoverFormat = _dereq_('../../plots/cartesian/axis_format_attributes').axisHoverFormat;
var hovertemplateAttrs = _dereq_('../../plots/template_attributes').hovertemplateAttrs;
var colorScaleAttrs = _dereq_('../../components/colorscale/attributes');
var extendFlat = _dereq_('../../lib/extend').extendFlat;
module.exports = extendFlat(
{
x: histogramAttrs.x,
y: histogramAttrs.y,
z: {
valType: 'data_array',
editType: 'calc',
},
marker: {
color: {
valType: 'data_array',
editType: 'calc',
},
editType: 'calc'
},
histnorm: histogramAttrs.histnorm,
histfunc: histogramAttrs.histfunc,
nbinsx: histogramAttrs.nbinsx,
xbins: makeBinAttrs('x'),
nbinsy: histogramAttrs.nbinsy,
ybins: makeBinAttrs('y'),
autobinx: histogramAttrs.autobinx,
autobiny: histogramAttrs.autobiny,
bingroup: extendFlat({}, histogramAttrs.bingroup, {
}),
xbingroup: extendFlat({}, histogramAttrs.bingroup, {
}),
ybingroup: extendFlat({}, histogramAttrs.bingroup, {
}),
xgap: heatmapAttrs.xgap,
ygap: heatmapAttrs.ygap,
zsmooth: heatmapAttrs.zsmooth,
xhoverformat: axisHoverFormat('x'),
yhoverformat: axisHoverFormat('y'),
zhoverformat: axisHoverFormat('z', 1),
hovertemplate: hovertemplateAttrs({}, {keys: 'z'}),
showlegend: extendFlat({}, baseAttrs.showlegend, {dflt: false})
},
colorScaleAttrs('', {cLetter: 'z', autoColorDflt: false})
);
},{"../../components/colorscale/attributes":373,"../../lib/extend":493,"../../plots/attributes":550,"../../plots/cartesian/axis_format_attributes":557,"../../plots/template_attributes":633,"../heatmap/attributes":792,"../histogram/attributes":811,"../histogram/bin_attributes":813}],825:[function(_dereq_,module,exports){
'use strict';
var Lib = _dereq_('../../lib');
var Axes = _dereq_('../../plots/cartesian/axes');
var binFunctions = _dereq_('../histogram/bin_functions');
var normFunctions = _dereq_('../histogram/norm_functions');
var doAvg = _dereq_('../histogram/average');
var getBinSpanLabelRound = _dereq_('../histogram/bin_label_vals');
var calcAllAutoBins = _dereq_('../histogram/calc').calcAllAutoBins;
module.exports = function calc(gd, trace) {
var xa = Axes.getFromId(gd, trace.xaxis);
var ya = Axes.getFromId(gd, trace.yaxis);
var xcalendar = trace.xcalendar;
var ycalendar = trace.ycalendar;
var xr2c = function(v) { return xa.r2c(v, 0, xcalendar); };
var yr2c = function(v) { return ya.r2c(v, 0, ycalendar); };
var xc2r = function(v) { return xa.c2r(v, 0, xcalendar); };
var yc2r = function(v) { return ya.c2r(v, 0, ycalendar); };
var i, j, n, m;
// calculate the bins
var xBinsAndPos = calcAllAutoBins(gd, trace, xa, 'x');
var xBinSpec = xBinsAndPos[0];
var xPos0 = xBinsAndPos[1];
var yBinsAndPos = calcAllAutoBins(gd, trace, ya, 'y');
var yBinSpec = yBinsAndPos[0];
var yPos0 = yBinsAndPos[1];
var serieslen = trace._length;
if(xPos0.length > serieslen) xPos0.splice(serieslen, xPos0.length - serieslen);
if(yPos0.length > serieslen) yPos0.splice(serieslen, yPos0.length - serieslen);
// make the empty bin array & scale the map
var z = [];
var onecol = [];
var zerocol = [];
var nonuniformBinsX = typeof xBinSpec.size === 'string';
var nonuniformBinsY = typeof yBinSpec.size === 'string';
var xEdges = [];
var yEdges = [];
var xbins = nonuniformBinsX ? xEdges : xBinSpec;
var ybins = nonuniformBinsY ? yEdges : yBinSpec;
var total = 0;
var counts = [];
var inputPoints = [];
var norm = trace.histnorm;
var func = trace.histfunc;
var densitynorm = norm.indexOf('density') !== -1;
var extremefunc = func === 'max' || func === 'min';
var sizeinit = extremefunc ? null : 0;
var binfunc = binFunctions.count;
var normfunc = normFunctions[norm];
var doavg = false;
var xinc = [];
var yinc = [];
// set a binning function other than count?
// for binning functions: check first for 'z',
// then 'mc' in case we had a colored scatter plot
// and want to transfer these colors to the 2D histo
// TODO: axe this, make it the responsibility of the app changing type? or an impliedEdit?
var rawCounterData = ('z' in trace) ?
trace.z :
(('marker' in trace && Array.isArray(trace.marker.color)) ?
trace.marker.color : '');
if(rawCounterData && func !== 'count') {
doavg = func === 'avg';
binfunc = binFunctions[func];
}
// decrease end a little in case of rounding errors
var xBinSize = xBinSpec.size;
var xBinStart = xr2c(xBinSpec.start);
var xBinEnd = xr2c(xBinSpec.end) +
(xBinStart - Axes.tickIncrement(xBinStart, xBinSize, false, xcalendar)) / 1e6;
for(i = xBinStart; i < xBinEnd; i = Axes.tickIncrement(i, xBinSize, false, xcalendar)) {
onecol.push(sizeinit);
xEdges.push(i);
if(doavg) zerocol.push(0);
}
xEdges.push(i);
var nx = onecol.length;
var dx = (i - xBinStart) / nx;
var x0 = xc2r(xBinStart + dx / 2);
var yBinSize = yBinSpec.size;
var yBinStart = yr2c(yBinSpec.start);
var yBinEnd = yr2c(yBinSpec.end) +
(yBinStart - Axes.tickIncrement(yBinStart, yBinSize, false, ycalendar)) / 1e6;
for(i = yBinStart; i < yBinEnd; i = Axes.tickIncrement(i, yBinSize, false, ycalendar)) {
z.push(onecol.slice());
yEdges.push(i);
var ipCol = new Array(nx);
for(j = 0; j < nx; j++) ipCol[j] = [];
inputPoints.push(ipCol);
if(doavg) counts.push(zerocol.slice());
}
yEdges.push(i);
var ny = z.length;
var dy = (i - yBinStart) / ny;
var y0 = yc2r(yBinStart + dy / 2);
if(densitynorm) {
xinc = makeIncrements(onecol.length, xbins, dx, nonuniformBinsX);
yinc = makeIncrements(z.length, ybins, dy, nonuniformBinsY);
}
// for date axes we need bin bounds to be calcdata. For nonuniform bins
// we already have this, but uniform with start/end/size they're still strings.
if(!nonuniformBinsX && xa.type === 'date') xbins = binsToCalc(xr2c, xbins);
if(!nonuniformBinsY && ya.type === 'date') ybins = binsToCalc(yr2c, ybins);
// put data into bins
var uniqueValsPerX = true;
var uniqueValsPerY = true;
var xVals = new Array(nx);
var yVals = new Array(ny);
var xGapLow = Infinity;
var xGapHigh = Infinity;
var yGapLow = Infinity;
var yGapHigh = Infinity;
for(i = 0; i < serieslen; i++) {
var xi = xPos0[i];
var yi = yPos0[i];
n = Lib.findBin(xi, xbins);
m = Lib.findBin(yi, ybins);
if(n >= 0 && n < nx && m >= 0 && m < ny) {
total += binfunc(n, i, z[m], rawCounterData, counts[m]);
inputPoints[m][n].push(i);
if(uniqueValsPerX) {
if(xVals[n] === undefined) xVals[n] = xi;
else if(xVals[n] !== xi) uniqueValsPerX = false;
}
if(uniqueValsPerY) {
if(yVals[m] === undefined) yVals[m] = yi;
else if(yVals[m] !== yi) uniqueValsPerY = false;
}
xGapLow = Math.min(xGapLow, xi - xEdges[n]);
xGapHigh = Math.min(xGapHigh, xEdges[n + 1] - xi);
yGapLow = Math.min(yGapLow, yi - yEdges[m]);
yGapHigh = Math.min(yGapHigh, yEdges[m + 1] - yi);
}
}
// normalize, if needed
if(doavg) {
for(m = 0; m < ny; m++) total += doAvg(z[m], counts[m]);
}
if(normfunc) {
for(m = 0; m < ny; m++) normfunc(z[m], total, xinc, yinc[m]);
}
return {
x: xPos0,
xRanges: getRanges(xEdges, uniqueValsPerX && xVals, xGapLow, xGapHigh, xa, xcalendar),
x0: x0,
dx: dx,
y: yPos0,
yRanges: getRanges(yEdges, uniqueValsPerY && yVals, yGapLow, yGapHigh, ya, ycalendar),
y0: y0,
dy: dy,
z: z,
pts: inputPoints
};
};
function makeIncrements(len, bins, dv, nonuniform) {
var out = new Array(len);
var i;
if(nonuniform) {
for(i = 0; i < len; i++) out[i] = 1 / (bins[i + 1] - bins[i]);
} else {
var inc = 1 / dv;
for(i = 0; i < len; i++) out[i] = inc;
}
return out;
}
function binsToCalc(r2c, bins) {
return {
start: r2c(bins.start),
end: r2c(bins.end),
size: bins.size
};
}
function getRanges(edges, uniqueVals, gapLow, gapHigh, ax, calendar) {
var i;
var len = edges.length - 1;
var out = new Array(len);
var roundFn = getBinSpanLabelRound(gapLow, gapHigh, edges, ax, calendar);
for(i = 0; i < len; i++) {
var v = (uniqueVals || [])[i];
out[i] = v === undefined ?
[roundFn(edges[i]), roundFn(edges[i + 1], true)] :
[v, v];
}
return out;
}
},{"../../lib":503,"../../plots/cartesian/axes":554,"../histogram/average":812,"../histogram/bin_functions":814,"../histogram/bin_label_vals":815,"../histogram/calc":816,"../histogram/norm_functions":823}],826:[function(_dereq_,module,exports){
'use strict';
var Lib = _dereq_('../../lib');
var handleSampleDefaults = _dereq_('./sample_defaults');
var handleStyleDefaults = _dereq_('../heatmap/style_defaults');
var colorscaleDefaults = _dereq_('../../components/colorscale/defaults');
var attributes = _dereq_('./attributes');
module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout) {
function coerce(attr, dflt) {
return Lib.coerce(traceIn, traceOut, attributes, attr, dflt);
}
handleSampleDefaults(traceIn, traceOut, coerce, layout);
if(traceOut.visible === false) return;
handleStyleDefaults(traceIn, traceOut, coerce, layout);
colorscaleDefaults(traceIn, traceOut, layout, coerce, {prefix: '', cLetter: 'z'});
coerce('hovertemplate');
coerce('xhoverformat');
coerce('yhoverformat');
};
},{"../../components/colorscale/defaults":376,"../../lib":503,"../heatmap/style_defaults":805,"./attributes":824,"./sample_defaults":829}],827:[function(_dereq_,module,exports){
'use strict';
var heatmapHover = _dereq_('../heatmap/hover');
var hoverLabelText = _dereq_('../../plots/cartesian/axes').hoverLabelText;
module.exports = function hoverPoints(pointData, xval, yval, hovermode, opts) {
var pts = heatmapHover(pointData, xval, yval, hovermode, opts);
if(!pts) return;
pointData = pts[0];
var indices = pointData.index;
var ny = indices[0];
var nx = indices[1];
var cd0 = pointData.cd[0];
var trace = cd0.trace;
var xRange = cd0.xRanges[nx];
var yRange = cd0.yRanges[ny];
pointData.xLabel = hoverLabelText(pointData.xa, [xRange[0], xRange[1]], trace.xhoverformat);
pointData.yLabel = hoverLabelText(pointData.ya, [yRange[0], yRange[1]], trace.yhoverformat);
return pts;
};
},{"../../plots/cartesian/axes":554,"../heatmap/hover":799}],828:[function(_dereq_,module,exports){
'use strict';
module.exports = {
attributes: _dereq_('./attributes'),
supplyDefaults: _dereq_('./defaults'),
crossTraceDefaults: _dereq_('../histogram/cross_trace_defaults'),
calc: _dereq_('../heatmap/calc'),
plot: _dereq_('../heatmap/plot'),
layerName: 'heatmaplayer',
colorbar: _dereq_('../heatmap/colorbar'),
style: _dereq_('../heatmap/style'),
hoverPoints: _dereq_('./hover'),
eventData: _dereq_('../histogram/event_data'),
moduleType: 'trace',
name: 'histogram2d',
basePlotModule: _dereq_('../../plots/cartesian'),
categories: ['cartesian', 'svg', '2dMap', 'histogram', 'showLegend'],
meta: {
}
};
},{"../../plots/cartesian":568,"../heatmap/calc":793,"../heatmap/colorbar":795,"../heatmap/plot":803,"../heatmap/style":804,"../histogram/cross_trace_defaults":818,"../histogram/event_data":820,"./attributes":824,"./defaults":826,"./hover":827}],829:[function(_dereq_,module,exports){
'use strict';
var Registry = _dereq_('../../registry');
var Lib = _dereq_('../../lib');
module.exports = function handleSampleDefaults(traceIn, traceOut, coerce, layout) {
var x = coerce('x');
var y = coerce('y');
var xlen = Lib.minRowLength(x);
var ylen = Lib.minRowLength(y);
// we could try to accept x0 and dx, etc...
// but that's a pretty weird use case.
// for now require both x and y explicitly specified.
if(!xlen || !ylen) {
traceOut.visible = false;
return;
}
traceOut._length = Math.min(xlen, ylen);
var handleCalendarDefaults = Registry.getComponentMethod('calendars', 'handleTraceDefaults');
handleCalendarDefaults(traceIn, traceOut, ['x', 'y'], layout);
// if marker.color is an array, we can use it in aggregation instead of z
var hasAggregationData = coerce('z') || coerce('marker.color');
if(hasAggregationData) coerce('histfunc');
coerce('histnorm');
// Note: bin defaults are now handled in Histogram2D.crossTraceDefaults
// autobin(x|y) are only included here to appease Plotly.validate
coerce('autobinx');
coerce('autobiny');
};
},{"../../lib":503,"../../registry":638}],830:[function(_dereq_,module,exports){
'use strict';
var histogram2dAttrs = _dereq_('../histogram2d/attributes');
var contourAttrs = _dereq_('../contour/attributes');
var colorScaleAttrs = _dereq_('../../components/colorscale/attributes');
var axisHoverFormat = _dereq_('../../plots/cartesian/axis_format_attributes').axisHoverFormat;
var extendFlat = _dereq_('../../lib/extend').extendFlat;
module.exports = extendFlat({
x: histogram2dAttrs.x,
y: histogram2dAttrs.y,
z: histogram2dAttrs.z,
marker: histogram2dAttrs.marker,
histnorm: histogram2dAttrs.histnorm,
histfunc: histogram2dAttrs.histfunc,
nbinsx: histogram2dAttrs.nbinsx,
xbins: histogram2dAttrs.xbins,
nbinsy: histogram2dAttrs.nbinsy,
ybins: histogram2dAttrs.ybins,
autobinx: histogram2dAttrs.autobinx,
autobiny: histogram2dAttrs.autobiny,
bingroup: histogram2dAttrs.bingroup,
xbingroup: histogram2dAttrs.xbingroup,
ybingroup: histogram2dAttrs.ybingroup,
autocontour: contourAttrs.autocontour,
ncontours: contourAttrs.ncontours,
contours: contourAttrs.contours,
line: {
color: contourAttrs.line.color,
width: extendFlat({}, contourAttrs.line.width, {
dflt: 0.5,
}),
dash: contourAttrs.line.dash,
smoothing: contourAttrs.line.smoothing,
editType: 'plot'
},
xhoverformat: axisHoverFormat('x'),
yhoverformat: axisHoverFormat('y'),
zhoverformat: axisHoverFormat('z', 1),
hovertemplate: histogram2dAttrs.hovertemplate
},
colorScaleAttrs('', {
cLetter: 'z',
editTypeOverride: 'calc'
})
);
},{"../../components/colorscale/attributes":373,"../../lib/extend":493,"../../plots/cartesian/axis_format_attributes":557,"../contour/attributes":735,"../histogram2d/attributes":824}],831:[function(_dereq_,module,exports){
'use strict';
var Lib = _dereq_('../../lib');
var handleSampleDefaults = _dereq_('../histogram2d/sample_defaults');
var handleContoursDefaults = _dereq_('../contour/contours_defaults');
var handleStyleDefaults = _dereq_('../contour/style_defaults');
var attributes = _dereq_('./attributes');
module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout) {
function coerce(attr, dflt) {
return Lib.coerce(traceIn, traceOut, attributes, attr, dflt);
}
function coerce2(attr) {
return Lib.coerce2(traceIn, traceOut, attributes, attr);
}
handleSampleDefaults(traceIn, traceOut, coerce, layout);
if(traceOut.visible === false) return;
handleContoursDefaults(traceIn, traceOut, coerce, coerce2);
handleStyleDefaults(traceIn, traceOut, coerce, layout);
coerce('hovertemplate');
coerce('xhoverformat');
coerce('yhoverformat');
};
},{"../../lib":503,"../contour/contours_defaults":742,"../contour/style_defaults":756,"../histogram2d/sample_defaults":829,"./attributes":830}],832:[function(_dereq_,module,exports){
'use strict';
module.exports = {
attributes: _dereq_('./attributes'),
supplyDefaults: _dereq_('./defaults'),
crossTraceDefaults: _dereq_('../histogram/cross_trace_defaults'),
calc: _dereq_('../contour/calc'),
plot: _dereq_('../contour/plot').plot,
layerName: 'contourlayer',
style: _dereq_('../contour/style'),
colorbar: _dereq_('../contour/colorbar'),
hoverPoints: _dereq_('../contour/hover'),
moduleType: 'trace',
name: 'histogram2dcontour',
basePlotModule: _dereq_('../../plots/cartesian'),
categories: ['cartesian', 'svg', '2dMap', 'contour', 'histogram', 'showLegend'],
meta: {
}
};
},{"../../plots/cartesian":568,"../contour/calc":736,"../contour/colorbar":738,"../contour/hover":748,"../contour/plot":753,"../contour/style":755,"../histogram/cross_trace_defaults":818,"./attributes":830,"./defaults":831}],833:[function(_dereq_,module,exports){
'use strict';
var hovertemplateAttrs = _dereq_('../../plots/template_attributes').hovertemplateAttrs;
var texttemplateAttrs = _dereq_('../../plots/template_attributes').texttemplateAttrs;
var colorScaleAttrs = _dereq_('../../components/colorscale/attributes');
var domainAttrs = _dereq_('../../plots/domain').attributes;
var pieAttrs = _dereq_('../pie/attributes');
var sunburstAttrs = _dereq_('../sunburst/attributes');
var treemapAttrs = _dereq_('../treemap/attributes');
var constants = _dereq_('../treemap/constants');
var extendFlat = _dereq_('../../lib/extend').extendFlat;
module.exports = {
labels: sunburstAttrs.labels,
parents: sunburstAttrs.parents,
values: sunburstAttrs.values,
branchvalues: sunburstAttrs.branchvalues,
count: sunburstAttrs.count,
level: sunburstAttrs.level,
maxdepth: sunburstAttrs.maxdepth,
tiling: {
orientation: {
valType: 'enumerated',
values: ['v', 'h'],
dflt: 'h',
editType: 'plot',
},
flip: treemapAttrs.tiling.flip,
pad: {
valType: 'number',
min: 0,
dflt: 0,
editType: 'plot',
},
editType: 'calc',
},
marker: extendFlat({
colors: sunburstAttrs.marker.colors,
line: sunburstAttrs.marker.line,
editType: 'calc'
},
colorScaleAttrs('marker', {
colorAttr: 'colors',
anim: false // TODO: set to anim: true?
})
),
leaf: sunburstAttrs.leaf,
pathbar: treemapAttrs.pathbar,
text: pieAttrs.text,
textinfo: sunburstAttrs.textinfo,
// TODO: incorporate `label` and `value` in the eventData
texttemplate: texttemplateAttrs({editType: 'plot'}, {
keys: constants.eventDataKeys.concat(['label', 'value'])
}),
hovertext: pieAttrs.hovertext,
hoverinfo: sunburstAttrs.hoverinfo,
hovertemplate: hovertemplateAttrs({}, {
keys: constants.eventDataKeys
}),
textfont: pieAttrs.textfont,
insidetextfont: pieAttrs.insidetextfont,
outsidetextfont: treemapAttrs.outsidetextfont,
textposition: treemapAttrs.textposition,
sort: pieAttrs.sort,
root: sunburstAttrs.root,
domain: domainAttrs({name: 'icicle', trace: true, editType: 'calc'}),
};
},{"../../components/colorscale/attributes":373,"../../lib/extend":493,"../../plots/domain":584,"../../plots/template_attributes":633,"../pie/attributes":899,"../sunburst/attributes":1044,"../treemap/attributes":1070,"../treemap/constants":1073}],834:[function(_dereq_,module,exports){
'use strict';
var plots = _dereq_('../../plots/plots');
exports.name = 'icicle';
exports.plot = function(gd, traces, transitionOpts, makeOnCompleteCallback) {
plots.plotBasePlot(exports.name, gd, traces, transitionOpts, makeOnCompleteCallback);
};
exports.clean = function(newFullData, newFullLayout, oldFullData, oldFullLayout) {
plots.cleanBasePlot(exports.name, newFullData, newFullLayout, oldFullData, oldFullLayout);
};
},{"../../plots/plots":619}],835:[function(_dereq_,module,exports){
'use strict';
var calc = _dereq_('../sunburst/calc');
exports.calc = function(gd, trace) {
return calc.calc(gd, trace);
};
exports.crossTraceCalc = function(gd) {
return calc._runCrossTraceCalc('icicle', gd);
};
},{"../sunburst/calc":1046}],836:[function(_dereq_,module,exports){
'use strict';
var Lib = _dereq_('../../lib');
var attributes = _dereq_('./attributes');
var Color = _dereq_('../../components/color');
var handleDomainDefaults = _dereq_('../../plots/domain').defaults;
var handleText = _dereq_('../bar/defaults').handleText;
var TEXTPAD = _dereq_('../bar/constants').TEXTPAD;
var Colorscale = _dereq_('../../components/colorscale');
var hasColorscale = Colorscale.hasColorscale;
var colorscaleDefaults = Colorscale.handleDefaults;
module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout) {
function coerce(attr, dflt) {
return Lib.coerce(traceIn, traceOut, attributes, attr, dflt);
}
var labels = coerce('labels');
var parents = coerce('parents');
if(!labels || !labels.length || !parents || !parents.length) {
traceOut.visible = false;
return;
}
var vals = coerce('values');
if(vals && vals.length) {
coerce('branchvalues');
} else {
coerce('count');
}
coerce('level');
coerce('maxdepth');
coerce('tiling.orientation');
coerce('tiling.flip');
coerce('tiling.pad');
var text = coerce('text');
coerce('texttemplate');
if(!traceOut.texttemplate) coerce('textinfo', Array.isArray(text) ? 'text+label' : 'label');
coerce('hovertext');
coerce('hovertemplate');
var hasPathbar = coerce('pathbar.visible');
var textposition = 'auto';
handleText(traceIn, traceOut, layout, coerce, textposition, {
hasPathbar: hasPathbar,
moduleHasSelected: false,
moduleHasUnselected: false,
moduleHasConstrain: false,
moduleHasCliponaxis: false,
moduleHasTextangle: false,
moduleHasInsideanchor: false
});
coerce('textposition');
var lineWidth = coerce('marker.line.width');
if(lineWidth) coerce('marker.line.color', layout.paper_bgcolor);
coerce('marker.colors');
var withColorscale = traceOut._hasColorscale = (
hasColorscale(traceIn, 'marker', 'colors') ||
(traceIn.marker || {}).coloraxis // N.B. special logic to consider "values" colorscales
);
if(withColorscale) {
colorscaleDefaults(traceIn, traceOut, layout, coerce, {prefix: 'marker.', cLetter: 'c'});
}
coerce('leaf.opacity', withColorscale ? 1 : 0.7);
traceOut._hovered = {
marker: {
line: {
width: 2,
color: Color.contrast(layout.paper_bgcolor)
}
}
};
if(hasPathbar) {
// This works even for multi-line labels as icicle pathbar trim out line breaks
coerce('pathbar.thickness', traceOut.pathbar.textfont.size + 2 * TEXTPAD);
coerce('pathbar.side');
coerce('pathbar.edgeshape');
}
coerce('sort');
coerce('root.color');
handleDomainDefaults(traceOut, layout, coerce);
// do not support transforms for now
traceOut._length = null;
};
},{"../../components/color":366,"../../components/colorscale":378,"../../lib":503,"../../plots/domain":584,"../bar/constants":650,"../bar/defaults":652,"./attributes":833}],837:[function(_dereq_,module,exports){
'use strict';
var d3 = _dereq_('@plotly/d3');
var Lib = _dereq_('../../lib');
var Drawing = _dereq_('../../components/drawing');
var svgTextUtils = _dereq_('../../lib/svg_text_utils');
var partition = _dereq_('./partition');
var styleOne = _dereq_('./style').styleOne;
var constants = _dereq_('../treemap/constants');
var helpers = _dereq_('../sunburst/helpers');
var attachFxHandlers = _dereq_('../sunburst/fx');
var formatSliceLabel = _dereq_('../sunburst/plot').formatSliceLabel;
var onPathbar = false; // for Descendants
module.exports = function drawDescendants(gd, cd, entry, slices, opts) {
var width = opts.width;
var height = opts.height;
var viewX = opts.viewX;
var viewY = opts.viewY;
var pathSlice = opts.pathSlice;
var toMoveInsideSlice = opts.toMoveInsideSlice;
var strTransform = opts.strTransform;
var hasTransition = opts.hasTransition;
var handleSlicesExit = opts.handleSlicesExit;
var makeUpdateSliceInterpolator = opts.makeUpdateSliceInterpolator;
var makeUpdateTextInterpolator = opts.makeUpdateTextInterpolator;
var prevEntry = opts.prevEntry;
var refRect = {};
var fullLayout = gd._fullLayout;
var cd0 = cd[0];
var trace = cd0.trace;
var hasLeft = trace.textposition.indexOf('left') !== -1;
var hasRight = trace.textposition.indexOf('right') !== -1;
var hasBottom = trace.textposition.indexOf('bottom') !== -1;
// N.B. slice data isn't the calcdata,
// grab corresponding calcdata item in sliceData[i].data.data
var allData = partition(entry, [width, height], {
flipX: trace.tiling.flip.indexOf('x') > -1,
flipY: trace.tiling.flip.indexOf('y') > -1,
orientation: trace.tiling.orientation,
pad: {
inner: trace.tiling.pad
},
maxDepth: trace._maxDepth
});
var sliceData = allData.descendants();
var minVisibleDepth = Infinity;
var maxVisibleDepth = -Infinity;
sliceData.forEach(function(pt) {
var depth = pt.depth;
if(depth >= trace._maxDepth) {
// hide slices that won't show up on graph
pt.x0 = pt.x1 = (pt.x0 + pt.x1) / 2;
pt.y0 = pt.y1 = (pt.y0 + pt.y1) / 2;
} else {
minVisibleDepth = Math.min(minVisibleDepth, depth);
maxVisibleDepth = Math.max(maxVisibleDepth, depth);
}
});
slices = slices.data(sliceData, helpers.getPtId);
trace._maxVisibleLayers = isFinite(maxVisibleDepth) ? maxVisibleDepth - minVisibleDepth + 1 : 0;
slices.enter().append('g')
.classed('slice', true);
handleSlicesExit(slices, onPathbar, refRect, [width, height], pathSlice);
slices.order();
// next coords of previous entry
var nextOfPrevEntry = null;
if(hasTransition && prevEntry) {
var prevEntryId = helpers.getPtId(prevEntry);
slices.each(function(pt) {
if(nextOfPrevEntry === null && (helpers.getPtId(pt) === prevEntryId)) {
nextOfPrevEntry = {
x0: pt.x0,
x1: pt.x1,
y0: pt.y0,
y1: pt.y1
};
}
});
}
var getRefRect = function() {
return nextOfPrevEntry || {
x0: 0,
x1: width,
y0: 0,
y1: height
};
};
var updateSlices = slices;
if(hasTransition) {
updateSlices = updateSlices.transition().each('end', function() {
// N.B. gd._transitioning is (still) *true* by the time
// transition updates get here
var sliceTop = d3.select(this);
helpers.setSliceCursor(sliceTop, gd, {
hideOnRoot: true,
hideOnLeaves: false,
isTransitioning: false
});
});
}
updateSlices.each(function(pt) {
// for bbox
pt._x0 = viewX(pt.x0);
pt._x1 = viewX(pt.x1);
pt._y0 = viewY(pt.y0);
pt._y1 = viewY(pt.y1);
pt._hoverX = viewX(pt.x1 - trace.tiling.pad),
pt._hoverY = hasBottom ?
viewY(pt.y1 - trace.tiling.pad / 2) :
viewY(pt.y0 + trace.tiling.pad / 2);
var sliceTop = d3.select(this);
var slicePath = Lib.ensureSingle(sliceTop, 'path', 'surface', function(s) {
s.style('pointer-events', 'all');
});
if(hasTransition) {
slicePath.transition().attrTween('d', function(pt2) {
var interp = makeUpdateSliceInterpolator(
pt2,
onPathbar,
getRefRect(),
[width, height],
{
orientation: trace.tiling.orientation,
flipX: trace.tiling.flip.indexOf('x') > -1,
flipY: trace.tiling.flip.indexOf('y') > -1,
}
);
return function(t) { return pathSlice(interp(t)); };
});
} else {
slicePath.attr('d', pathSlice);
}
sliceTop
.call(attachFxHandlers, entry, gd, cd, {
styleOne: styleOne,
eventDataKeys: constants.eventDataKeys,
transitionTime: constants.CLICK_TRANSITION_TIME,
transitionEasing: constants.CLICK_TRANSITION_EASING
})
.call(helpers.setSliceCursor, gd, { isTransitioning: gd._transitioning });
slicePath.call(styleOne, pt, trace, {
hovered: false
});
if(pt.x0 === pt.x1 || pt.y0 === pt.y1) {
pt._text = '';
} else {
pt._text = formatSliceLabel(pt, entry, trace, cd, fullLayout) || '';
}
var sliceTextGroup = Lib.ensureSingle(sliceTop, 'g', 'slicetext');
var sliceText = Lib.ensureSingle(sliceTextGroup, 'text', '', function(s) {
// prohibit tex interpretation until we can handle
// tex and regular text together
s.attr('data-notex', 1);
});
var font = Lib.ensureUniformFontSize(gd, helpers.determineTextFont(trace, pt, fullLayout.font));
sliceText.text(pt._text || ' ') // use one space character instead of a blank string to avoid jumps during transition
.classed('slicetext', true)
.attr('text-anchor', hasRight ? 'end' : hasLeft ? 'start' : 'middle')
.call(Drawing.font, font)
.call(svgTextUtils.convertToTspans, gd);
pt.textBB = Drawing.bBox(sliceText.node());
pt.transform = toMoveInsideSlice(pt, {
fontSize: font.size
});
pt.transform.fontSize = font.size;
if(hasTransition) {
sliceText.transition().attrTween('transform', function(pt2) {
var interp = makeUpdateTextInterpolator(pt2, onPathbar, getRefRect(), [width, height]);
return function(t) { return strTransform(interp(t)); };
});
} else {
sliceText.attr('transform', strTransform(pt));
}
});
return nextOfPrevEntry;
};
},{"../../components/drawing":388,"../../lib":503,"../../lib/svg_text_utils":529,"../sunburst/fx":1049,"../sunburst/helpers":1050,"../sunburst/plot":1054,"../treemap/constants":1073,"./partition":841,"./style":843,"@plotly/d3":58}],838:[function(_dereq_,module,exports){
'use strict';
module.exports = {
moduleType: 'trace',
name: 'icicle',
basePlotModule: _dereq_('./base_plot'),
categories: [],
animatable: true,
attributes: _dereq_('./attributes'),
layoutAttributes: _dereq_('./layout_attributes'),
supplyDefaults: _dereq_('./defaults'),
supplyLayoutDefaults: _dereq_('./layout_defaults'),
calc: _dereq_('./calc').calc,
crossTraceCalc: _dereq_('./calc').crossTraceCalc,
plot: _dereq_('./plot'),
style: _dereq_('./style').style,
colorbar: _dereq_('../scatter/marker_colorbar'),
meta: {
}
};
},{"../scatter/marker_colorbar":943,"./attributes":833,"./base_plot":834,"./calc":835,"./defaults":836,"./layout_attributes":839,"./layout_defaults":840,"./plot":842,"./style":843}],839:[function(_dereq_,module,exports){
'use strict';
module.exports = {
iciclecolorway: {
valType: 'colorlist',
editType: 'calc',
},
extendiciclecolors: {
valType: 'boolean',
dflt: true,
editType: 'calc',
}
};
},{}],840:[function(_dereq_,module,exports){
'use strict';
var Lib = _dereq_('../../lib');
var layoutAttributes = _dereq_('./layout_attributes');
module.exports = function supplyLayoutDefaults(layoutIn, layoutOut) {
function coerce(attr, dflt) {
return Lib.coerce(layoutIn, layoutOut, layoutAttributes, attr, dflt);
}
coerce('iciclecolorway', layoutOut.colorway);
coerce('extendiciclecolors');
};
},{"../../lib":503,"./layout_attributes":839}],841:[function(_dereq_,module,exports){
'use strict';
var d3Hierarchy = _dereq_('d3-hierarchy');
var flipTree = _dereq_('../treemap/flip_tree');
module.exports = function partition(entry, size, opts) {
var flipX = opts.flipX;
var flipY = opts.flipY;
var swapXY = opts.orientation === 'h';
var maxDepth = opts.maxDepth;
var newWidth = size[0];
var newHeight = size[1];
if(maxDepth) {
newWidth = (entry.height + 1) * size[0] / Math.min(entry.height + 1, maxDepth);
newHeight = (entry.height + 1) * size[1] / Math.min(entry.height + 1, maxDepth);
}
var result = d3Hierarchy
.partition()
.padding(opts.pad.inner)
.size(
swapXY ? [size[1], newWidth] : [size[0], newHeight]
)(entry);
if(swapXY || flipX || flipY) {
flipTree(result, size, {
swapXY: swapXY,
flipX: flipX,
flipY: flipY
});
}
return result;
};
},{"../treemap/flip_tree":1078,"d3-hierarchy":115}],842:[function(_dereq_,module,exports){
'use strict';
var draw = _dereq_('../treemap/draw');
var drawDescendants = _dereq_('./draw_descendants');
module.exports = function _plot(gd, cdmodule, transitionOpts, makeOnCompleteCallback) {
return draw(gd, cdmodule, transitionOpts, makeOnCompleteCallback, {
type: 'icicle',
drawDescendants: drawDescendants
});
};
},{"../treemap/draw":1075,"./draw_descendants":837}],843:[function(_dereq_,module,exports){
'use strict';
var d3 = _dereq_('@plotly/d3');
var Color = _dereq_('../../components/color');
var Lib = _dereq_('../../lib');
var resizeText = _dereq_('../bar/uniform_text').resizeText;
function style(gd) {
var s = gd._fullLayout._iciclelayer.selectAll('.trace');
resizeText(gd, s, 'icicle');
s.each(function(cd) {
var gTrace = d3.select(this);
var cd0 = cd[0];
var trace = cd0.trace;
gTrace.style('opacity', trace.opacity);
gTrace.selectAll('path.surface').each(function(pt) {
d3.select(this).call(styleOne, pt, trace);
});
});
}
function styleOne(s, pt, trace) {
var cdi = pt.data.data;
var isLeaf = !pt.children;
var ptNumber = cdi.i;
var lineColor = Lib.castOption(trace, ptNumber, 'marker.line.color') || Color.defaultLine;
var lineWidth = Lib.castOption(trace, ptNumber, 'marker.line.width') || 0;
s.style('stroke-width', lineWidth)
.call(Color.fill, cdi.color)
.call(Color.stroke, lineColor)
.style('opacity', isLeaf ? trace.leaf.opacity : null);
}
module.exports = {
style: style,
styleOne: styleOne
};
},{"../../components/color":366,"../../lib":503,"../bar/uniform_text":664,"@plotly/d3":58}],844:[function(_dereq_,module,exports){
'use strict';
var baseAttrs = _dereq_('../../plots/attributes');
var hovertemplateAttrs = _dereq_('../../plots/template_attributes').hovertemplateAttrs;
var extendFlat = _dereq_('../../lib/extend').extendFlat;
var colormodel = _dereq_('./constants').colormodel;
var cm = ['rgb', 'rgba', 'rgba256', 'hsl', 'hsla'];
var zminDesc = [];
var zmaxDesc = [];
for(var i = 0; i < cm.length; i++) {
var cr = colormodel[cm[i]];
zminDesc.push('For the `' + cm[i] + '` colormodel, it is [' + (cr.zminDflt || cr.min).join(', ') + '].');
zmaxDesc.push('For the `' + cm[i] + '` colormodel, it is [' + (cr.zmaxDflt || cr.max).join(', ') + '].');
}
module.exports = extendFlat({
source: {
valType: 'string',
editType: 'calc',
},
z: {
valType: 'data_array',
editType: 'calc',
},
colormodel: {
valType: 'enumerated',
values: cm,
editType: 'calc',
},
zsmooth: {
valType: 'enumerated',
values: ['fast', false],
dflt: false,
editType: 'plot',
},
zmin: {
valType: 'info_array',
items: [
{valType: 'number', editType: 'calc'},
{valType: 'number', editType: 'calc'},
{valType: 'number', editType: 'calc'},
{valType: 'number', editType: 'calc'}
],
editType: 'calc',
},
zmax: {
valType: 'info_array',
items: [
{valType: 'number', editType: 'calc'},
{valType: 'number', editType: 'calc'},
{valType: 'number', editType: 'calc'},
{valType: 'number', editType: 'calc'}
],
editType: 'calc',
},
x0: {
valType: 'any',
dflt: 0,
editType: 'calc+clearAxisTypes',
},
y0: {
valType: 'any',
dflt: 0,
editType: 'calc+clearAxisTypes',
},
dx: {
valType: 'number',
dflt: 1,
editType: 'calc',
},
dy: {
valType: 'number',
dflt: 1,
editType: 'calc',
},
text: {
valType: 'data_array',
editType: 'plot',
},
hovertext: {
valType: 'data_array',
editType: 'plot',
},
hoverinfo: extendFlat({}, baseAttrs.hoverinfo, {
flags: ['x', 'y', 'z', 'color', 'name', 'text'],
dflt: 'x+y+z+text+name'
}),
hovertemplate: hovertemplateAttrs({}, {
keys: ['z', 'color', 'colormodel']
}),
transforms: undefined
});
},{"../../lib/extend":493,"../../plots/attributes":550,"../../plots/template_attributes":633,"./constants":846}],845:[function(_dereq_,module,exports){
'use strict';
var Lib = _dereq_('../../lib');
var constants = _dereq_('./constants');
var isNumeric = _dereq_('fast-isnumeric');
var Axes = _dereq_('../../plots/cartesian/axes');
var maxRowLength = _dereq_('../../lib').maxRowLength;
var getImageSize = _dereq_('./helpers').getImageSize;
module.exports = function calc(gd, trace) {
var h;
var w;
if(trace._hasZ) {
h = trace.z.length;
w = maxRowLength(trace.z);
} else if(trace._hasSource) {
var size = getImageSize(trace.source);
h = size.height;
w = size.width;
}
var xa = Axes.getFromId(gd, trace.xaxis || 'x');
var ya = Axes.getFromId(gd, trace.yaxis || 'y');
var x0 = xa.d2c(trace.x0) - trace.dx / 2;
var y0 = ya.d2c(trace.y0) - trace.dy / 2;
// Set axis range
var i;
var xrange = [x0, x0 + w * trace.dx];
var yrange = [y0, y0 + h * trace.dy];
if(xa && xa.type === 'log') for(i = 0; i < w; i++) xrange.push(x0 + i * trace.dx);
if(ya && ya.type === 'log') for(i = 0; i < h; i++) yrange.push(y0 + i * trace.dy);
trace._extremes[xa._id] = Axes.findExtremes(xa, xrange);
trace._extremes[ya._id] = Axes.findExtremes(ya, yrange);
trace._scaler = makeScaler(trace);
var cd0 = {
x0: x0,
y0: y0,
z: trace.z,
w: w,
h: h
};
return [cd0];
};
function scale(zero, ratio, min, max) {
return function(c) {
return Lib.constrain((c - zero) * ratio, min, max);
};
}
function constrain(min, max) {
return function(c) { return Lib.constrain(c, min, max);};
}
// Generate a function to scale color components according to zmin/zmax and the colormodel
function makeScaler(trace) {
var cr = constants.colormodel[trace.colormodel];
var colormodel = (cr.colormodel || trace.colormodel);
var n = colormodel.length;
trace._sArray = [];
// Loop over all color components
for(var k = 0; k < n; k++) {
if(cr.min[k] !== trace.zmin[k] || cr.max[k] !== trace.zmax[k]) {
trace._sArray.push(scale(
trace.zmin[k],
(cr.max[k] - cr.min[k]) / (trace.zmax[k] - trace.zmin[k]),
cr.min[k],
cr.max[k]
));
} else {
trace._sArray.push(constrain(cr.min[k], cr.max[k]));
}
}
return function(pixel) {
var c = pixel.slice(0, n);
for(var k = 0; k < n; k++) {
var ck = c[k];
if(!isNumeric(ck)) return false;
c[k] = trace._sArray[k](ck);
}
return c;
};
}
},{"../../lib":503,"../../plots/cartesian/axes":554,"./constants":846,"./helpers":849,"fast-isnumeric":190}],846:[function(_dereq_,module,exports){
'use strict';
module.exports = {
colormodel: {
// min and max define the numerical range accepted in CSS
// If z(min|max)Dflt are not defined, z(min|max) will default to min/max
rgb: {
min: [0, 0, 0],
max: [255, 255, 255],
fmt: function(c) {return c.slice(0, 3);},
suffix: ['', '', '']
},
rgba: {
min: [0, 0, 0, 0],
max: [255, 255, 255, 1],
fmt: function(c) {return c.slice(0, 4);},
suffix: ['', '', '', '']
},
rgba256: {
colormodel: 'rgba', // because rgba256 is not an accept colormodel in CSS
zminDflt: [0, 0, 0, 0],
zmaxDflt: [255, 255, 255, 255],
min: [0, 0, 0, 0],
max: [255, 255, 255, 1],
fmt: function(c) {return c.slice(0, 4);},
suffix: ['', '', '', '']
},
hsl: {
min: [0, 0, 0],
max: [360, 100, 100],
fmt: function(c) {
var p = c.slice(0, 3);
p[1] = p[1] + '%';
p[2] = p[2] + '%';
return p;
},
suffix: ['°', '%', '%']
},
hsla: {
min: [0, 0, 0, 0],
max: [360, 100, 100, 1],
fmt: function(c) {
var p = c.slice(0, 4);
p[1] = p[1] + '%';
p[2] = p[2] + '%';
return p;
},
suffix: ['°', '%', '%', '']
}
},
// For pixelated image rendering
// http://phrogz.net/tmp/canvas_image_zoom.html
// https://developer.mozilla.org/en-US/docs/Web/CSS/image-rendering
pixelatedStyle: [
'image-rendering: optimizeSpeed',
'image-rendering: -moz-crisp-edges',
'image-rendering: -o-crisp-edges',
'image-rendering: -webkit-optimize-contrast',
'image-rendering: optimize-contrast',
'image-rendering: crisp-edges',
'image-rendering: pixelated',
''
].join('; ')
};
},{}],847:[function(_dereq_,module,exports){
'use strict';
var Lib = _dereq_('../../lib');
var attributes = _dereq_('./attributes');
var constants = _dereq_('./constants');
var dataUri = _dereq_('../../snapshot/helpers').IMAGE_URL_PREFIX;
module.exports = function supplyDefaults(traceIn, traceOut) {
function coerce(attr, dflt) {
return Lib.coerce(traceIn, traceOut, attributes, attr, dflt);
}
coerce('source');
// sanitize source to only allow for data URI representing images
if(traceOut.source && !traceOut.source.match(dataUri)) delete traceOut.source;
traceOut._hasSource = !!traceOut.source;
var z = coerce('z');
traceOut._hasZ = !(z === undefined || !z.length || !z[0] || !z[0].length);
if(!traceOut._hasZ && !traceOut._hasSource) {
traceOut.visible = false;
return;
}
coerce('x0');
coerce('y0');
coerce('dx');
coerce('dy');
var cm;
if(traceOut._hasZ) {
coerce('colormodel', 'rgb');
cm = constants.colormodel[traceOut.colormodel];
coerce('zmin', (cm.zminDflt || cm.min));
coerce('zmax', (cm.zmaxDflt || cm.max));
} else if(traceOut._hasSource) {
traceOut.colormodel = 'rgba256';
cm = constants.colormodel[traceOut.colormodel];
traceOut.zmin = cm.zminDflt;
traceOut.zmax = cm.zmaxDflt;
}
coerce('zsmooth');
coerce('text');
coerce('hovertext');
coerce('hovertemplate');
traceOut._length = null;
};
},{"../../lib":503,"../../snapshot/helpers":642,"./attributes":844,"./constants":846}],848:[function(_dereq_,module,exports){
'use strict';
module.exports = function eventData(out, pt) {
if('xVal' in pt) out.x = pt.xVal;
if('yVal' in pt) out.y = pt.yVal;
if(pt.xa) out.xaxis = pt.xa;
if(pt.ya) out.yaxis = pt.ya;
out.color = pt.color;
out.colormodel = pt.trace.colormodel;
if(!out.z) out.z = pt.color;
return out;
};
},{}],849:[function(_dereq_,module,exports){
'use strict';
var probeSync = _dereq_('probe-image-size/sync');
var dataUri = _dereq_('../../snapshot/helpers').IMAGE_URL_PREFIX;
var Buffer = _dereq_('buffer/').Buffer; // note: the trailing slash is important!
exports.getImageSize = function(src) {
var data = src.replace(dataUri, '');
var buff = new Buffer(data, 'base64');
return probeSync(buff);
};
},{"../../snapshot/helpers":642,"buffer/":85,"probe-image-size/sync":276}],850:[function(_dereq_,module,exports){
'use strict';
var Fx = _dereq_('../../components/fx');
var Lib = _dereq_('../../lib');
var constants = _dereq_('./constants');
module.exports = function hoverPoints(pointData, xval, yval) {
var cd0 = pointData.cd[0];
var trace = cd0.trace;
var xa = pointData.xa;
var ya = pointData.ya;
// Return early if not on image
if(Fx.inbox(xval - cd0.x0, xval - (cd0.x0 + cd0.w * trace.dx), 0) > 0 ||
Fx.inbox(yval - cd0.y0, yval - (cd0.y0 + cd0.h * trace.dy), 0) > 0) {
return;
}
// Find nearest pixel's index
var nx = Math.floor((xval - cd0.x0) / trace.dx);
var ny = Math.floor(Math.abs(yval - cd0.y0) / trace.dy);
var pixel;
if(trace._hasZ) {
pixel = cd0.z[ny][nx];
} else if(trace._hasSource) {
pixel = trace._canvas.el.getContext('2d').getImageData(nx, ny, 1, 1).data;
}
// return early if pixel is undefined
if(!pixel) return;
var hoverinfo = cd0.hi || trace.hoverinfo;
var fmtColor;
if(hoverinfo) {
var parts = hoverinfo.split('+');
if(parts.indexOf('all') !== -1) parts = ['color'];
if(parts.indexOf('color') !== -1) fmtColor = true;
}
var cr = constants.colormodel[trace.colormodel];
var colormodel = cr.colormodel || trace.colormodel;
var dims = colormodel.length;
var c = trace._scaler(pixel);
var s = cr.suffix;
var colorstring = [];
if(trace.hovertemplate || fmtColor) {
colorstring.push('[' + [c[0] + s[0], c[1] + s[1], c[2] + s[2]].join(', '));
if(dims === 4) colorstring.push(', ' + c[3] + s[3]);
colorstring.push(']');
colorstring = colorstring.join('');
pointData.extraText = colormodel.toUpperCase() + ': ' + colorstring;
}
var text;
if(Array.isArray(trace.hovertext) && Array.isArray(trace.hovertext[ny])) {
text = trace.hovertext[ny][nx];
} else if(Array.isArray(trace.text) && Array.isArray(trace.text[ny])) {
text = trace.text[ny][nx];
}
// TODO: for color model with 3 dims, display something useful for hovertemplate `%{color[3]}`
var py = ya.c2p(cd0.y0 + (ny + 0.5) * trace.dy);
var xVal = cd0.x0 + (nx + 0.5) * trace.dx;
var yVal = cd0.y0 + (ny + 0.5) * trace.dy;
var zLabel = '[' + pixel.slice(0, trace.colormodel.length).join(', ') + ']';
return [Lib.extendFlat(pointData, {
index: [ny, nx],
x0: xa.c2p(cd0.x0 + nx * trace.dx),
x1: xa.c2p(cd0.x0 + (nx + 1) * trace.dx),
y0: py,
y1: py,
color: c,
xVal: xVal,
xLabelVal: xVal,
yVal: yVal,
yLabelVal: yVal,
zLabelVal: zLabel,
text: text,
hovertemplateLabels: {
'zLabel': zLabel,
'colorLabel': colorstring,
'color[0]Label': c[0] + s[0],
'color[1]Label': c[1] + s[1],
'color[2]Label': c[2] + s[2],
'color[3]Label': c[3] + s[3]
}
})];
};
},{"../../components/fx":406,"../../lib":503,"./constants":846}],851:[function(_dereq_,module,exports){
'use strict';
module.exports = {
attributes: _dereq_('./attributes'),
supplyDefaults: _dereq_('./defaults'),
calc: _dereq_('./calc'),
plot: _dereq_('./plot'),
style: _dereq_('./style'),
hoverPoints: _dereq_('./hover'),
eventData: _dereq_('./event_data'),
moduleType: 'trace',
name: 'image',
basePlotModule: _dereq_('../../plots/cartesian'),
categories: ['cartesian', 'svg', '2dMap', 'noSortingByValue'],
animatable: false,
meta: {
}
};
},{"../../plots/cartesian":568,"./attributes":844,"./calc":845,"./defaults":847,"./event_data":848,"./hover":850,"./plot":852,"./style":853}],852:[function(_dereq_,module,exports){
'use strict';
var d3 = _dereq_('@plotly/d3');
var Lib = _dereq_('../../lib');
var strTranslate = Lib.strTranslate;
var xmlnsNamespaces = _dereq_('../../constants/xmlns_namespaces');
var constants = _dereq_('./constants');
var unsupportedBrowsers = Lib.isIOS() || Lib.isSafari() || Lib.isIE();
module.exports = function plot(gd, plotinfo, cdimage, imageLayer) {
var xa = plotinfo.xaxis;
var ya = plotinfo.yaxis;
var supportsPixelatedImage = !(unsupportedBrowsers || gd._context._exportedPlot);
Lib.makeTraceGroups(imageLayer, cdimage, 'im').each(function(cd) {
var plotGroup = d3.select(this);
var cd0 = cd[0];
var trace = cd0.trace;
var realImage = (
((trace.zsmooth === 'fast') || (trace.zsmooth === false && supportsPixelatedImage)) &&
!trace._hasZ && trace._hasSource && xa.type === 'linear' && ya.type === 'linear'
);
trace._realImage = realImage;
var z = cd0.z;
var x0 = cd0.x0;
var y0 = cd0.y0;
var w = cd0.w;
var h = cd0.h;
var dx = trace.dx;
var dy = trace.dy;
var left, right, temp, top, bottom, i;
// in case of log of a negative
i = 0;
while(left === undefined && i < w) {
left = xa.c2p(x0 + i * dx);
i++;
}
i = w;
while(right === undefined && i > 0) {
right = xa.c2p(x0 + i * dx);
i--;
}
i = 0;
while(top === undefined && i < h) {
top = ya.c2p(y0 + i * dy);
i++;
}
i = h;
while(bottom === undefined && i > 0) {
bottom = ya.c2p(y0 + i * dy);
i--;
}
if(right < left) {
temp = right;
right = left;
left = temp;
}
if(bottom < top) {
temp = top;
top = bottom;
bottom = temp;
}
// Reduce image size when zoomed in to save memory
if(!realImage) {
var extra = 0.5; // half the axis size
left = Math.max(-extra * xa._length, left);
right = Math.min((1 + extra) * xa._length, right);
top = Math.max(-extra * ya._length, top);
bottom = Math.min((1 + extra) * ya._length, bottom);
}
var imageWidth = Math.round(right - left);
var imageHeight = Math.round(bottom - top);
// if image is entirely off-screen, don't even draw it
var isOffScreen = (imageWidth <= 0 || imageHeight <= 0);
if(isOffScreen) {
var noImage = plotGroup.selectAll('image').data([]);
noImage.exit().remove();
return;
}
// Create a new canvas and draw magnified pixels on it
function drawMagnifiedPixelsOnCanvas(readPixel) {
var canvas = document.createElement('canvas');
canvas.width = imageWidth;
canvas.height = imageHeight;
var context = canvas.getContext('2d');
var ipx = function(i) {return Lib.constrain(Math.round(xa.c2p(x0 + i * dx) - left), 0, imageWidth);};
var jpx = function(j) {return Lib.constrain(Math.round(ya.c2p(y0 + j * dy) - top), 0, imageHeight);};
var cr = constants.colormodel[trace.colormodel];
var colormodel = (cr.colormodel || trace.colormodel);
var fmt = cr.fmt;
var c;
for(i = 0; i < cd0.w; i++) {
var ipx0 = ipx(i); var ipx1 = ipx(i + 1);
if(ipx1 === ipx0 || isNaN(ipx1) || isNaN(ipx0)) continue;
for(var j = 0; j < cd0.h; j++) {
var jpx0 = jpx(j); var jpx1 = jpx(j + 1);
if(jpx1 === jpx0 || isNaN(jpx1) || isNaN(jpx0) || !readPixel(i, j)) continue;
c = trace._scaler(readPixel(i, j));
if(c) {
context.fillStyle = colormodel + '(' + fmt(c).join(',') + ')';
} else {
// Return a transparent pixel
context.fillStyle = 'rgba(0,0,0,0)';
}
context.fillRect(ipx0, jpx0, ipx1 - ipx0, jpx1 - jpx0);
}
}
return canvas;
}
var image3 = plotGroup.selectAll('image')
.data([cd]);
image3.enter().append('svg:image').attr({
xmlns: xmlnsNamespaces.svg,
preserveAspectRatio: 'none'
});
image3.exit().remove();
var style = (trace.zsmooth === false) ? constants.pixelatedStyle : '';
if(realImage) {
var xRange = Lib.simpleMap(xa.range, xa.r2l);
var yRange = Lib.simpleMap(ya.range, ya.r2l);
var flipX = xRange[1] < xRange[0];
var flipY = yRange[1] > yRange[0];
if(flipX || flipY) {
var tx = left + imageWidth / 2;
var ty = top + imageHeight / 2;
style += 'transform:' +
strTranslate(tx + 'px', ty + 'px') +
'scale(' + (flipX ? -1 : 1) + ',' + (flipY ? -1 : 1) + ')' +
strTranslate(-tx + 'px', -ty + 'px') + ';';
}
}
image3.attr('style', style);
var p = new Promise(function(resolve) {
if(trace._hasZ) {
resolve();
} else if(trace._hasSource) {
// Check if canvas already exists and has the right data
if(
trace._canvas &&
trace._canvas.el.width === w &&
trace._canvas.el.height === h &&
trace._canvas.source === trace.source
) {
resolve();
} else {
// Create a canvas and transfer image onto it to access pixel information
var canvas = document.createElement('canvas');
canvas.width = w;
canvas.height = h;
var context = canvas.getContext('2d');
trace._image = trace._image || new Image();
var image = trace._image;
image.onload = function() {
context.drawImage(image, 0, 0);
trace._canvas = {
el: canvas,
source: trace.source
};
resolve();
};
image.setAttribute('src', trace.source);
}
}
})
.then(function() {
var href, canvas;
if(trace._hasZ) {
canvas = drawMagnifiedPixelsOnCanvas(function(i, j) {return z[j][i];});
href = canvas.toDataURL('image/png');
} else if(trace._hasSource) {
if(realImage) {
href = trace.source;
} else {
var context = trace._canvas.el.getContext('2d');
var data = context.getImageData(0, 0, w, h).data;
canvas = drawMagnifiedPixelsOnCanvas(function(i, j) {
var index = 4 * (j * w + i);
return [
data[index],
data[index + 1],
data[index + 2],
data[index + 3]
];
});
href = canvas.toDataURL('image/png');
}
}
image3.attr({
'xlink:href': href,
height: imageHeight,
width: imageWidth,
x: left,
y: top
});
});
gd._promises.push(p);
});
};
},{"../../constants/xmlns_namespaces":480,"../../lib":503,"./constants":846,"@plotly/d3":58}],853:[function(_dereq_,module,exports){
'use strict';
var d3 = _dereq_('@plotly/d3');
module.exports = function style(gd) {
d3.select(gd).selectAll('.im image')
.style('opacity', function(d) {
return d[0].trace.opacity;
});
};
},{"@plotly/d3":58}],854:[function(_dereq_,module,exports){
'use strict';
var extendFlat = _dereq_('../../lib/extend').extendFlat;
var extendDeep = _dereq_('../../lib/extend').extendDeep;
var overrideAll = _dereq_('../../plot_api/edit_types').overrideAll;
var fontAttrs = _dereq_('../../plots/font_attributes');
var colorAttrs = _dereq_('../../components/color/attributes');
var domainAttrs = _dereq_('../../plots/domain').attributes;
var axesAttrs = _dereq_('../../plots/cartesian/layout_attributes');
var templatedArray = _dereq_('../../plot_api/plot_template').templatedArray;
var delta = _dereq_('../../constants/delta.js');
var descriptionOnlyNumbers = _dereq_('../../plots/cartesian/axis_format_attributes').descriptionOnlyNumbers;
var textFontAttrs = fontAttrs({
editType: 'plot',
colorEditType: 'plot'
});
var gaugeBarAttrs = {
color: {
valType: 'color',
editType: 'plot',
},
line: {
color: {
valType: 'color',
dflt: colorAttrs.defaultLine,
editType: 'plot',
},
width: {
valType: 'number',
min: 0,
dflt: 0,
editType: 'plot',
},
editType: 'calc'
},
thickness: {
valType: 'number',
min: 0,
max: 1,
dflt: 1,
editType: 'plot',
},
editType: 'calc'
};
var rangeAttr = {
valType: 'info_array',
items: [
{valType: 'number', editType: 'plot'},
{valType: 'number', editType: 'plot'}
],
editType: 'plot',
};
var stepsAttrs = templatedArray('step', extendDeep({}, gaugeBarAttrs, {
range: rangeAttr
}));
module.exports = {
mode: {
valType: 'flaglist',
editType: 'calc',
flags: ['number', 'delta', 'gauge'],
dflt: 'number',
},
value: {
valType: 'number',
editType: 'calc',
anim: true,
},
align: {
valType: 'enumerated',
values: ['left', 'center', 'right'],
editType: 'plot',
},
// position
domain: domainAttrs({name: 'indicator', trace: true, editType: 'calc'}),
title: {
text: {
valType: 'string',
editType: 'plot',
},
align: {
valType: 'enumerated',
values: ['left', 'center', 'right'],
editType: 'plot',
},
font: extendFlat({}, textFontAttrs, {
}),
editType: 'plot'
},
number: {
valueformat: {
valType: 'string',
dflt: '',
editType: 'plot',
description: descriptionOnlyNumbers('value')
},
font: extendFlat({}, textFontAttrs, {
}),
prefix: {
valType: 'string',
dflt: '',
editType: 'plot',
},
suffix: {
valType: 'string',
dflt: '',
editType: 'plot',
},
editType: 'plot'
},
delta: {
reference: {
valType: 'number',
editType: 'calc',
},
position: {
valType: 'enumerated',
values: ['top', 'bottom', 'left', 'right'],
dflt: 'bottom',
editType: 'plot',
},
relative: {
valType: 'boolean',
editType: 'plot',
dflt: false,
},
valueformat: {
valType: 'string',
editType: 'plot',
description: descriptionOnlyNumbers('value')
},
increasing: {
symbol: {
valType: 'string',
dflt: delta.INCREASING.SYMBOL,
editType: 'plot',
},
color: {
valType: 'color',
dflt: delta.INCREASING.COLOR,
editType: 'plot',
},
// TODO: add attribute to show sign
editType: 'plot'
},
decreasing: {
symbol: {
valType: 'string',
dflt: delta.DECREASING.SYMBOL,
editType: 'plot',
},
color: {
valType: 'color',
dflt: delta.DECREASING.COLOR,
editType: 'plot',
},
// TODO: add attribute to hide sign
editType: 'plot'
},
font: extendFlat({}, textFontAttrs, {
}),
editType: 'calc'
},
gauge: {
shape: {
valType: 'enumerated',
editType: 'plot',
dflt: 'angular',
values: ['angular', 'bullet'],
},
bar: extendDeep({}, gaugeBarAttrs, {
color: {dflt: 'green'},
}),
// Background of the gauge
bgcolor: {
valType: 'color',
editType: 'plot',
},
bordercolor: {
valType: 'color',
dflt: colorAttrs.defaultLine,
editType: 'plot',
},
borderwidth: {
valType: 'number',
min: 0,
dflt: 1,
editType: 'plot',
},
axis: overrideAll({
range: rangeAttr,
visible: extendFlat({}, axesAttrs.visible, {
dflt: true
}),
// tick and title properties named and function exactly as in axes
tickmode: axesAttrs.tickmode,
nticks: axesAttrs.nticks,
tick0: axesAttrs.tick0,
dtick: axesAttrs.dtick,
tickvals: axesAttrs.tickvals,
ticktext: axesAttrs.ticktext,
ticks: extendFlat({}, axesAttrs.ticks, {dflt: 'outside'}),
ticklen: axesAttrs.ticklen,
tickwidth: axesAttrs.tickwidth,
tickcolor: axesAttrs.tickcolor,
showticklabels: axesAttrs.showticklabels,
tickfont: fontAttrs({
}),
tickangle: axesAttrs.tickangle,
tickformat: axesAttrs.tickformat,
tickformatstops: axesAttrs.tickformatstops,
tickprefix: axesAttrs.tickprefix,
showtickprefix: axesAttrs.showtickprefix,
ticksuffix: axesAttrs.ticksuffix,
showticksuffix: axesAttrs.showticksuffix,
separatethousands: axesAttrs.separatethousands,
exponentformat: axesAttrs.exponentformat,
minexponent: axesAttrs.minexponent,
showexponent: axesAttrs.showexponent,
editType: 'plot'
}, 'plot'),
// Steps (or ranges) and thresholds
steps: stepsAttrs,
threshold: {
line: {
color: extendFlat({}, gaugeBarAttrs.line.color, {
}),
width: extendFlat({}, gaugeBarAttrs.line.width, {
dflt: 1,
}),
editType: 'plot'
},
thickness: extendFlat({}, gaugeBarAttrs.thickness, {
dflt: 0.85,
}),
value: {
valType: 'number',
editType: 'calc',
dflt: false,
},
editType: 'plot'
},
editType: 'plot'
// TODO: in future version, add marker: (bar|needle)
}
};
},{"../../components/color/attributes":365,"../../constants/delta.js":473,"../../lib/extend":493,"../../plot_api/edit_types":536,"../../plot_api/plot_template":543,"../../plots/cartesian/axis_format_attributes":557,"../../plots/cartesian/layout_attributes":569,"../../plots/domain":584,"../../plots/font_attributes":585}],855:[function(_dereq_,module,exports){
'use strict';
var plots = _dereq_('../../plots/plots');
exports.name = 'indicator';
exports.plot = function(gd, traces, transitionOpts, makeOnCompleteCallback) {
plots.plotBasePlot(exports.name, gd, traces, transitionOpts, makeOnCompleteCallback);
};
exports.clean = function(newFullData, newFullLayout, oldFullData, oldFullLayout) {
plots.cleanBasePlot(exports.name, newFullData, newFullLayout, oldFullData, oldFullLayout);
};
},{"../../plots/plots":619}],856:[function(_dereq_,module,exports){
'use strict';
// var Lib = require('../../lib');
function calc(gd, trace) {
var cd = [];
var lastReading = trace.value;
if(!(typeof trace._lastValue === 'number')) trace._lastValue = trace.value;
var secondLastReading = trace._lastValue;
var deltaRef = secondLastReading;
if(trace._hasDelta && typeof trace.delta.reference === 'number') {
deltaRef = trace.delta.reference;
}
cd[0] = {
y: lastReading,
lastY: secondLastReading,
delta: lastReading - deltaRef,
relativeDelta: (lastReading - deltaRef) / deltaRef,
};
return cd;
}
module.exports = {
calc: calc
};
},{}],857:[function(_dereq_,module,exports){
'use strict';
module.exports = {
// Defaults for delta
defaultNumberFontSize: 80,
bulletNumberDomainSize: 0.25,
bulletPadding: 0.025,
innerRadius: 0.75,
valueThickness: 0.5, // thickness of value bars relative to full thickness,
titlePadding: 5,
horizontalPadding: 10
};
},{}],858:[function(_dereq_,module,exports){
'use strict';
var Lib = _dereq_('../../lib');
var attributes = _dereq_('./attributes');
var handleDomainDefaults = _dereq_('../../plots/domain').defaults;
var Template = _dereq_('../../plot_api/plot_template');
var handleArrayContainerDefaults = _dereq_('../../plots/array_container_defaults');
var cn = _dereq_('./constants.js');
var handleTickValueDefaults = _dereq_('../../plots/cartesian/tick_value_defaults');
var handleTickMarkDefaults = _dereq_('../../plots/cartesian/tick_mark_defaults');
var handleTickLabelDefaults = _dereq_('../../plots/cartesian/tick_label_defaults');
var handlePrefixSuffixDefaults = _dereq_('../../plots/cartesian/prefix_suffix_defaults');
function supplyDefaults(traceIn, traceOut, defaultColor, layout) {
function coerce(attr, dflt) {
return Lib.coerce(traceIn, traceOut, attributes, attr, dflt);
}
handleDomainDefaults(traceOut, layout, coerce);
// Mode
coerce('mode');
traceOut._hasNumber = traceOut.mode.indexOf('number') !== -1;
traceOut._hasDelta = traceOut.mode.indexOf('delta') !== -1;
traceOut._hasGauge = traceOut.mode.indexOf('gauge') !== -1;
var value = coerce('value');
traceOut._range = [0, (typeof value === 'number' ? 1.5 * value : 1)];
// Number attributes
var auto = new Array(2);
var bignumberFontSize;
if(traceOut._hasNumber) {
coerce('number.valueformat');
coerce('number.font.color', layout.font.color);
coerce('number.font.family', layout.font.family);
coerce('number.font.size');
if(traceOut.number.font.size === undefined) {
traceOut.number.font.size = cn.defaultNumberFontSize;
auto[0] = true;
}
coerce('number.prefix');
coerce('number.suffix');
bignumberFontSize = traceOut.number.font.size;
}
// delta attributes
var deltaFontSize;
if(traceOut._hasDelta) {
coerce('delta.font.color', layout.font.color);
coerce('delta.font.family', layout.font.family);
coerce('delta.font.size');
if(traceOut.delta.font.size === undefined) {
traceOut.delta.font.size = (traceOut._hasNumber ? 0.5 : 1) * (bignumberFontSize || cn.defaultNumberFontSize);
auto[1] = true;
}
coerce('delta.reference', traceOut.value);
coerce('delta.relative');
coerce('delta.valueformat', traceOut.delta.relative ? '2%' : '');
coerce('delta.increasing.symbol');
coerce('delta.increasing.color');
coerce('delta.decreasing.symbol');
coerce('delta.decreasing.color');
coerce('delta.position');
deltaFontSize = traceOut.delta.font.size;
}
traceOut._scaleNumbers = (!traceOut._hasNumber || auto[0]) && (!traceOut._hasDelta || auto[1]) || false;
// Title attributes
coerce('title.font.color', layout.font.color);
coerce('title.font.family', layout.font.family);
coerce('title.font.size', 0.25 * (bignumberFontSize || deltaFontSize || cn.defaultNumberFontSize));
coerce('title.text');
// Gauge attributes
var gaugeIn, gaugeOut, axisIn, axisOut;
function coerceGauge(attr, dflt) {
return Lib.coerce(gaugeIn, gaugeOut, attributes.gauge, attr, dflt);
}
function coerceGaugeAxis(attr, dflt) {
return Lib.coerce(axisIn, axisOut, attributes.gauge.axis, attr, dflt);
}
if(traceOut._hasGauge) {
gaugeIn = traceIn.gauge;
if(!gaugeIn) gaugeIn = {};
gaugeOut = Template.newContainer(traceOut, 'gauge');
coerceGauge('shape');
var isBullet = traceOut._isBullet = traceOut.gauge.shape === 'bullet';
if(!isBullet) {
coerce('title.align', 'center');
}
var isAngular = traceOut._isAngular = traceOut.gauge.shape === 'angular';
if(!isAngular) {
coerce('align', 'center');
}
// gauge background
coerceGauge('bgcolor', layout.paper_bgcolor);
coerceGauge('borderwidth');
coerceGauge('bordercolor');
// gauge bar indicator
coerceGauge('bar.color');
coerceGauge('bar.line.color');
coerceGauge('bar.line.width');
var defaultBarThickness = cn.valueThickness * (traceOut.gauge.shape === 'bullet' ? 0.5 : 1);
coerceGauge('bar.thickness', defaultBarThickness);
// Gauge steps
handleArrayContainerDefaults(gaugeIn, gaugeOut, {
name: 'steps',
handleItemDefaults: stepDefaults
});
// Gauge threshold
coerceGauge('threshold.value');
coerceGauge('threshold.thickness');
coerceGauge('threshold.line.width');
coerceGauge('threshold.line.color');
// Gauge axis
axisIn = {};
if(gaugeIn) axisIn = gaugeIn.axis || {};
axisOut = Template.newContainer(gaugeOut, 'axis');
coerceGaugeAxis('visible');
traceOut._range = coerceGaugeAxis('range', traceOut._range);
var opts = {outerTicks: true};
handleTickValueDefaults(axisIn, axisOut, coerceGaugeAxis, 'linear');
handlePrefixSuffixDefaults(axisIn, axisOut, coerceGaugeAxis, 'linear', opts);
handleTickLabelDefaults(axisIn, axisOut, coerceGaugeAxis, 'linear', opts);
handleTickMarkDefaults(axisIn, axisOut, coerceGaugeAxis, opts);
} else {
coerce('title.align', 'center');
coerce('align', 'center');
traceOut._isAngular = traceOut._isBullet = false;
}
// disable 1D transforms
traceOut._length = null;
}
function stepDefaults(stepIn, stepOut) {
function coerce(attr, dflt) {
return Lib.coerce(stepIn, stepOut, attributes.gauge.steps, attr, dflt);
}
coerce('color');
coerce('line.color');
coerce('line.width');
coerce('range');
coerce('thickness');
}
module.exports = {
supplyDefaults: supplyDefaults
};
},{"../../lib":503,"../../plot_api/plot_template":543,"../../plots/array_container_defaults":549,"../../plots/cartesian/prefix_suffix_defaults":573,"../../plots/cartesian/tick_label_defaults":578,"../../plots/cartesian/tick_mark_defaults":579,"../../plots/cartesian/tick_value_defaults":580,"../../plots/domain":584,"./attributes":854,"./constants.js":857}],859:[function(_dereq_,module,exports){
'use strict';
module.exports = {
moduleType: 'trace',
name: 'indicator',
basePlotModule: _dereq_('./base_plot'),
categories: ['svg', 'noOpacity', 'noHover'],
animatable: true,
attributes: _dereq_('./attributes'),
supplyDefaults: _dereq_('./defaults').supplyDefaults,
calc: _dereq_('./calc').calc,
plot: _dereq_('./plot'),
meta: {
}
};
},{"./attributes":854,"./base_plot":855,"./calc":856,"./defaults":858,"./plot":860}],860:[function(_dereq_,module,exports){
'use strict';
var d3 = _dereq_('@plotly/d3');
var interpolate = _dereq_('d3-interpolate').interpolate;
var interpolateNumber = _dereq_('d3-interpolate').interpolateNumber;
var Lib = _dereq_('../../lib');
var strScale = Lib.strScale;
var strTranslate = Lib.strTranslate;
var rad2deg = Lib.rad2deg;
var MID_SHIFT = _dereq_('../../constants/alignment').MID_SHIFT;
var Drawing = _dereq_('../../components/drawing');
var cn = _dereq_('./constants');
var svgTextUtils = _dereq_('../../lib/svg_text_utils');
var Axes = _dereq_('../../plots/cartesian/axes');
var handleAxisDefaults = _dereq_('../../plots/cartesian/axis_defaults');
var handleAxisPositionDefaults = _dereq_('../../plots/cartesian/position_defaults');
var axisLayoutAttrs = _dereq_('../../plots/cartesian/layout_attributes');
var Color = _dereq_('../../components/color');
var anchor = {
'left': 'start',
'center': 'middle',
'right': 'end'
};
var position = {
'left': 0,
'center': 0.5,
'right': 1
};
var SI_PREFIX = /[yzafpnµmkMGTPEZY]/;
function hasTransition(transitionOpts) {
// If transition config is provided, then it is only a partial replot and traces not
// updated are removed.
return transitionOpts && transitionOpts.duration > 0;
}
module.exports = function plot(gd, cdModule, transitionOpts, makeOnCompleteCallback) {
var fullLayout = gd._fullLayout;
var onComplete;
if(hasTransition(transitionOpts)) {
if(makeOnCompleteCallback) {
// If it was passed a callback to register completion, make a callback. If
// this is created, then it must be executed on completion, otherwise the
// pos-transition redraw will not execute:
onComplete = makeOnCompleteCallback();
}
}
Lib.makeTraceGroups(fullLayout._indicatorlayer, cdModule, 'trace').each(function(cd) {
var cd0 = cd[0];
var trace = cd0.trace;
var plotGroup = d3.select(this);
// Elements in trace
var hasGauge = trace._hasGauge;
var isAngular = trace._isAngular;
var isBullet = trace._isBullet;
// Domain size
var domain = trace.domain;
var size = {
w: fullLayout._size.w * (domain.x[1] - domain.x[0]),
h: fullLayout._size.h * (domain.y[1] - domain.y[0]),
l: fullLayout._size.l + fullLayout._size.w * domain.x[0],
r: fullLayout._size.r + fullLayout._size.w * (1 - domain.x[1]),
t: fullLayout._size.t + fullLayout._size.h * (1 - domain.y[1]),
b: fullLayout._size.b + fullLayout._size.h * (domain.y[0])
};
var centerX = size.l + size.w / 2;
var centerY = size.t + size.h / 2;
// Angular gauge size
var radius = Math.min(size.w / 2, size.h); // fill domain
var innerRadius = cn.innerRadius * radius;
// Position numbers based on mode and set the scaling logic
var numbersX, numbersY, numbersScaler;
var numbersAlign = trace.align || 'center';
numbersY = centerY;
if(!hasGauge) {
numbersX = size.l + position[numbersAlign] * size.w;
numbersScaler = function(el) {
return fitTextInsideBox(el, size.w, size.h);
};
} else {
if(isAngular) {
numbersX = centerX;
numbersY = centerY + radius / 2;
numbersScaler = function(el) {
return fitTextInsideCircle(el, 0.9 * innerRadius);
};
}
if(isBullet) {
var padding = cn.bulletPadding;
var p = (1 - cn.bulletNumberDomainSize) + padding;
numbersX = size.l + (p + (1 - p) * position[numbersAlign]) * size.w;
numbersScaler = function(el) {
return fitTextInsideBox(el, (cn.bulletNumberDomainSize - padding) * size.w, size.h);
};
}
}
// Draw numbers
drawNumbers(gd, plotGroup, cd, {
numbersX: numbersX,
numbersY: numbersY,
numbersScaler: numbersScaler,
transitionOpts: transitionOpts,
onComplete: onComplete
});
// Reexpress our gauge background attributes for drawing
var gaugeBg, gaugeOutline;
if(hasGauge) {
gaugeBg = {
range: trace.gauge.axis.range,
color: trace.gauge.bgcolor,
line: {
color: trace.gauge.bordercolor,
width: 0
},
thickness: 1
};
gaugeOutline = {
range: trace.gauge.axis.range,
color: 'rgba(0, 0, 0, 0)',
line: {
color: trace.gauge.bordercolor,
width: trace.gauge.borderwidth
},
thickness: 1
};
}
// Prepare angular gauge layers
var angularGauge = plotGroup.selectAll('g.angular').data(isAngular ? cd : []);
angularGauge.exit().remove();
var angularaxisLayer = plotGroup.selectAll('g.angularaxis').data(isAngular ? cd : []);
angularaxisLayer.exit().remove();
if(isAngular) {
drawAngularGauge(gd, plotGroup, cd, {
radius: radius,
innerRadius: innerRadius,
gauge: angularGauge,
layer: angularaxisLayer,
size: size,
gaugeBg: gaugeBg,
gaugeOutline: gaugeOutline,
transitionOpts: transitionOpts,
onComplete: onComplete
});
}
// Prepare bullet layers
var bulletGauge = plotGroup.selectAll('g.bullet').data(isBullet ? cd : []);
bulletGauge.exit().remove();
var bulletaxisLayer = plotGroup.selectAll('g.bulletaxis').data(isBullet ? cd : []);
bulletaxisLayer.exit().remove();
if(isBullet) {
drawBulletGauge(gd, plotGroup, cd, {
gauge: bulletGauge,
layer: bulletaxisLayer,
size: size,
gaugeBg: gaugeBg,
gaugeOutline: gaugeOutline,
transitionOpts: transitionOpts,
onComplete: onComplete
});
}
// title
var title = plotGroup.selectAll('text.title').data(cd);
title.exit().remove();
title.enter().append('text').classed('title', true);
title
.attr('text-anchor', function() {
return isBullet ? anchor.right : anchor[trace.title.align];
})
.text(trace.title.text)
.call(Drawing.font, trace.title.font)
.call(svgTextUtils.convertToTspans, gd);
// Position title
title.attr('transform', function() {
var titleX = size.l + size.w * position[trace.title.align];
var titleY;
var titlePadding = cn.titlePadding;
var titlebBox = Drawing.bBox(title.node());
if(hasGauge) {
if(isAngular) {
// position above axis ticks/labels
if(trace.gauge.axis.visible) {
var bBox = Drawing.bBox(angularaxisLayer.node());
titleY = (bBox.top - titlePadding) - titlebBox.bottom;
} else {
titleY = size.t + size.h / 2 - radius / 2 - titlebBox.bottom - titlePadding;
}
}
if(isBullet) {
// position outside domain
titleY = numbersY - (titlebBox.top + titlebBox.bottom) / 2;
titleX = size.l - cn.bulletPadding * size.w; // Outside domain, on the left
}
} else {
// position above numbers
titleY = (trace._numbersTop - titlePadding) - titlebBox.bottom;
}
return strTranslate(titleX, titleY);
});
});
};
function drawBulletGauge(gd, plotGroup, cd, opts) {
var trace = cd[0].trace;
var bullet = opts.gauge;
var axisLayer = opts.layer;
var gaugeBg = opts.gaugeBg;
var gaugeOutline = opts.gaugeOutline;
var size = opts.size;
var domain = trace.domain;
var transitionOpts = opts.transitionOpts;
var onComplete = opts.onComplete;
// preparing axis
var ax, vals, transFn, tickSign, shift;
// Enter bullet, axis
bullet.enter().append('g').classed('bullet', true);
bullet.attr('transform', strTranslate(size.l, size.t));
axisLayer.enter().append('g')
.classed('bulletaxis', true)
.classed('crisp', true);
axisLayer.selectAll('g.' + 'xbulletaxis' + 'tick,path,text').remove();
// Draw bullet
var bulletHeight = size.h; // use all vertical domain
var innerBulletHeight = trace.gauge.bar.thickness * bulletHeight;
var bulletLeft = domain.x[0];
var bulletRight = domain.x[0] + (domain.x[1] - domain.x[0]) * ((trace._hasNumber || trace._hasDelta) ? (1 - cn.bulletNumberDomainSize) : 1);
ax = mockAxis(gd, trace.gauge.axis);
ax._id = 'xbulletaxis';
ax.domain = [bulletLeft, bulletRight];
ax.setScale();
vals = Axes.calcTicks(ax);
transFn = Axes.makeTransTickFn(ax);
tickSign = Axes.getTickSigns(ax)[2];
shift = size.t + size.h;
if(ax.visible) {
Axes.drawTicks(gd, ax, {
vals: ax.ticks === 'inside' ? Axes.clipEnds(ax, vals) : vals,
layer: axisLayer,
path: Axes.makeTickPath(ax, shift, tickSign),
transFn: transFn
});
Axes.drawLabels(gd, ax, {
vals: vals,
layer: axisLayer,
transFn: transFn,
labelFns: Axes.makeLabelFns(ax, shift)
});
}
function drawRect(s) {
s
.attr('width', function(d) { return Math.max(0, ax.c2p(d.range[1]) - ax.c2p(d.range[0]));})
.attr('x', function(d) { return ax.c2p(d.range[0]);})
.attr('y', function(d) { return 0.5 * (1 - d.thickness) * bulletHeight;})
.attr('height', function(d) { return d.thickness * bulletHeight; });
}
// Draw bullet background, steps
var boxes = [gaugeBg].concat(trace.gauge.steps);
var bgBullet = bullet.selectAll('g.bg-bullet').data(boxes);
bgBullet.enter().append('g').classed('bg-bullet', true).append('rect');
bgBullet.select('rect')
.call(drawRect)
.call(styleShape);
bgBullet.exit().remove();
// Draw value bar with transitions
var fgBullet = bullet.selectAll('g.value-bullet').data([trace.gauge.bar]);
fgBullet.enter().append('g').classed('value-bullet', true).append('rect');
fgBullet.select('rect')
.attr('height', innerBulletHeight)
.attr('y', (bulletHeight - innerBulletHeight) / 2)
.call(styleShape);
if(hasTransition(transitionOpts)) {
fgBullet.select('rect')
.transition()
.duration(transitionOpts.duration)
.ease(transitionOpts.easing)
.each('end', function() { onComplete && onComplete(); })
.each('interrupt', function() { onComplete && onComplete(); })
.attr('width', Math.max(0, ax.c2p(Math.min(trace.gauge.axis.range[1], cd[0].y))));
} else {
fgBullet.select('rect')
.attr('width', typeof cd[0].y === 'number' ?
Math.max(0, ax.c2p(Math.min(trace.gauge.axis.range[1], cd[0].y))) :
0);
}
fgBullet.exit().remove();
var data = cd.filter(function() {return trace.gauge.threshold.value || trace.gauge.threshold.value === 0;});
var threshold = bullet.selectAll('g.threshold-bullet').data(data);
threshold.enter().append('g').classed('threshold-bullet', true).append('line');
threshold.select('line')
.attr('x1', ax.c2p(trace.gauge.threshold.value))
.attr('x2', ax.c2p(trace.gauge.threshold.value))
.attr('y1', (1 - trace.gauge.threshold.thickness) / 2 * bulletHeight)
.attr('y2', (1 - (1 - trace.gauge.threshold.thickness) / 2) * bulletHeight)
.call(Color.stroke, trace.gauge.threshold.line.color)
.style('stroke-width', trace.gauge.threshold.line.width);
threshold.exit().remove();
var bulletOutline = bullet.selectAll('g.gauge-outline').data([gaugeOutline]);
bulletOutline.enter().append('g').classed('gauge-outline', true).append('rect');
bulletOutline.select('rect')
.call(drawRect)
.call(styleShape);
bulletOutline.exit().remove();
}
function drawAngularGauge(gd, plotGroup, cd, opts) {
var trace = cd[0].trace;
var size = opts.size;
var radius = opts.radius;
var innerRadius = opts.innerRadius;
var gaugeBg = opts.gaugeBg;
var gaugeOutline = opts.gaugeOutline;
var gaugePosition = [size.l + size.w / 2, size.t + size.h / 2 + radius / 2];
var gauge = opts.gauge;
var axisLayer = opts.layer;
var transitionOpts = opts.transitionOpts;
var onComplete = opts.onComplete;
// circular gauge
var theta = Math.PI / 2;
function valueToAngle(v) {
var min = trace.gauge.axis.range[0];
var max = trace.gauge.axis.range[1];
var angle = (v - min) / (max - min) * Math.PI - theta;
if(angle < -theta) return -theta;
if(angle > theta) return theta;
return angle;
}
function arcPathGenerator(size) {
return d3.svg.arc()
.innerRadius((innerRadius + radius) / 2 - size / 2 * (radius - innerRadius))
.outerRadius((innerRadius + radius) / 2 + size / 2 * (radius - innerRadius))
.startAngle(-theta);
}
function drawArc(p) {
p
.attr('d', function(d) {
return arcPathGenerator(d.thickness)
.startAngle(valueToAngle(d.range[0]))
.endAngle(valueToAngle(d.range[1]))();
});
}
// preparing axis
var ax, vals, transFn, tickSign;
// Enter gauge and axis
gauge.enter().append('g').classed('angular', true);
gauge.attr('transform', strTranslate(gaugePosition[0], gaugePosition[1]));
axisLayer.enter().append('g')
.classed('angularaxis', true)
.classed('crisp', true);
axisLayer.selectAll('g.' + 'xangularaxis' + 'tick,path,text').remove();
ax = mockAxis(gd, trace.gauge.axis);
ax.type = 'linear';
ax.range = trace.gauge.axis.range;
ax._id = 'xangularaxis'; // or 'y', but I don't think this makes a difference here
ax.ticklabeloverflow = 'allow';
ax.setScale();
// 't'ick to 'g'eometric radians is used all over the place here
var t2g = function(d) {
return (ax.range[0] - d.x) / (ax.range[1] - ax.range[0]) * Math.PI + Math.PI;
};
var labelFns = {};
var out = Axes.makeLabelFns(ax, 0);
var labelStandoff = out.labelStandoff;
labelFns.xFn = function(d) {
var rad = t2g(d);
return Math.cos(rad) * labelStandoff;
};
labelFns.yFn = function(d) {
var rad = t2g(d);
var ff = Math.sin(rad) > 0 ? 0.2 : 1;
return -Math.sin(rad) * (labelStandoff + d.fontSize * ff) +
Math.abs(Math.cos(rad)) * (d.fontSize * MID_SHIFT);
};
labelFns.anchorFn = function(d) {
var rad = t2g(d);
var cos = Math.cos(rad);
return Math.abs(cos) < 0.1 ?
'middle' :
(cos > 0 ? 'start' : 'end');
};
labelFns.heightFn = function(d, a, h) {
var rad = t2g(d);
return -0.5 * (1 + Math.sin(rad)) * h;
};
var _transFn = function(rad) {
return strTranslate(
gaugePosition[0] + radius * Math.cos(rad),
gaugePosition[1] - radius * Math.sin(rad)
);
};
transFn = function(d) {
return _transFn(t2g(d));
};
var transFn2 = function(d) {
var rad = t2g(d);
return _transFn(rad) + 'rotate(' + -rad2deg(rad) + ')';
};
vals = Axes.calcTicks(ax);
tickSign = Axes.getTickSigns(ax)[2];
if(ax.visible) {
tickSign = ax.ticks === 'inside' ? -1 : 1;
var pad = (ax.linewidth || 1) / 2;
Axes.drawTicks(gd, ax, {
vals: vals,
layer: axisLayer,
path: 'M' + (tickSign * pad) + ',0h' + (tickSign * ax.ticklen),
transFn: transFn2
});
Axes.drawLabels(gd, ax, {
vals: vals,
layer: axisLayer,
transFn: transFn,
labelFns: labelFns
});
}
// Draw background + steps
var arcs = [gaugeBg].concat(trace.gauge.steps);
var bgArc = gauge.selectAll('g.bg-arc').data(arcs);
bgArc.enter().append('g').classed('bg-arc', true).append('path');
bgArc.select('path').call(drawArc).call(styleShape);
bgArc.exit().remove();
// Draw foreground with transition
var valueArcPathGenerator = arcPathGenerator(trace.gauge.bar.thickness);
var valueArc = gauge.selectAll('g.value-arc').data([trace.gauge.bar]);
valueArc.enter().append('g').classed('value-arc', true).append('path');
var valueArcPath = valueArc.select('path');
if(hasTransition(transitionOpts)) {
valueArcPath
.transition()
.duration(transitionOpts.duration)
.ease(transitionOpts.easing)
.each('end', function() { onComplete && onComplete(); })
.each('interrupt', function() { onComplete && onComplete(); })
.attrTween('d', arcTween(valueArcPathGenerator, valueToAngle(cd[0].lastY), valueToAngle(cd[0].y)));
trace._lastValue = cd[0].y;
} else {
valueArcPath.attr('d', typeof cd[0].y === 'number' ?
valueArcPathGenerator.endAngle(valueToAngle(cd[0].y)) :
'M0,0Z');
}
valueArcPath.call(styleShape);
valueArc.exit().remove();
// Draw threshold
arcs = [];
var v = trace.gauge.threshold.value;
if(v || v === 0) {
arcs.push({
range: [v, v],
color: trace.gauge.threshold.color,
line: {
color: trace.gauge.threshold.line.color,
width: trace.gauge.threshold.line.width
},
thickness: trace.gauge.threshold.thickness
});
}
var thresholdArc = gauge.selectAll('g.threshold-arc').data(arcs);
thresholdArc.enter().append('g').classed('threshold-arc', true).append('path');
thresholdArc.select('path').call(drawArc).call(styleShape);
thresholdArc.exit().remove();
// Draw border last
var gaugeBorder = gauge.selectAll('g.gauge-outline').data([gaugeOutline]);
gaugeBorder.enter().append('g').classed('gauge-outline', true).append('path');
gaugeBorder.select('path').call(drawArc).call(styleShape);
gaugeBorder.exit().remove();
}
function drawNumbers(gd, plotGroup, cd, opts) {
var trace = cd[0].trace;
var numbersX = opts.numbersX;
var numbersY = opts.numbersY;
var numbersAlign = trace.align || 'center';
var numbersAnchor = anchor[numbersAlign];
var transitionOpts = opts.transitionOpts;
var onComplete = opts.onComplete;
var numbers = Lib.ensureSingle(plotGroup, 'g', 'numbers');
var bignumberbBox, deltabBox;
var numbersbBox;
var data = [];
if(trace._hasNumber) data.push('number');
if(trace._hasDelta) {
data.push('delta');
if(trace.delta.position === 'left') data.reverse();
}
var sel = numbers.selectAll('text').data(data);
sel.enter().append('text');
sel
.attr('text-anchor', function() {return numbersAnchor;})
.attr('class', function(d) { return d;})
.attr('x', null)
.attr('y', null)
.attr('dx', null)
.attr('dy', null);
sel.exit().remove();
// Function to override the number formatting used during transitions
function transitionFormat(valueformat, fmt, from, to) {
// For now, do not display SI prefix if start and end value do not have any
if(valueformat.match('s') && // If using SI prefix
(from >= 0 !== to >= 0) && // If sign change
(!fmt(from).slice(-1).match(SI_PREFIX) && !fmt(to).slice(-1).match(SI_PREFIX)) // Has no SI prefix
) {
var transitionValueFormat = valueformat.slice().replace('s', 'f').replace(/\d+/, function(m) { return parseInt(m) - 1;});
var transitionAx = mockAxis(gd, {tickformat: transitionValueFormat});
return function(v) {
// Switch to fixed precision if number is smaller than one
if(Math.abs(v) < 1) return Axes.tickText(transitionAx, v).text;
return fmt(v);
};
} else {
return fmt;
}
}
function drawBignumber() {
var bignumberAx = mockAxis(gd, {tickformat: trace.number.valueformat}, trace._range);
bignumberAx.setScale();
Axes.prepTicks(bignumberAx);
var fmt = function(v) { return Axes.tickText(bignumberAx, v).text;};
var bignumberSuffix = trace.number.suffix;
var bignumberPrefix = trace.number.prefix;
var number = numbers.select('text.number');
function writeNumber() {
var txt = typeof cd[0].y === 'number' ?
bignumberPrefix + fmt(cd[0].y) + bignumberSuffix :
'-';
number.text(txt)
.call(Drawing.font, trace.number.font)
.call(svgTextUtils.convertToTspans, gd);
}
if(hasTransition(transitionOpts)) {
number
.transition()
.duration(transitionOpts.duration)
.ease(transitionOpts.easing)
.each('end', function() { writeNumber(); onComplete && onComplete(); })
.each('interrupt', function() { writeNumber(); onComplete && onComplete(); })
.attrTween('text', function() {
var that = d3.select(this);
var interpolator = interpolateNumber(cd[0].lastY, cd[0].y);
trace._lastValue = cd[0].y;
var transitionFmt = transitionFormat(trace.number.valueformat, fmt, cd[0].lastY, cd[0].y);
return function(t) {
that.text(bignumberPrefix + transitionFmt(interpolator(t)) + bignumberSuffix);
};
});
} else {
writeNumber();
}
bignumberbBox = measureText(bignumberPrefix + fmt(cd[0].y) + bignumberSuffix, trace.number.font, numbersAnchor, gd);
return number;
}
function drawDelta() {
var deltaAx = mockAxis(gd, {tickformat: trace.delta.valueformat}, trace._range);
deltaAx.setScale();
Axes.prepTicks(deltaAx);
var deltaFmt = function(v) { return Axes.tickText(deltaAx, v).text;};
var deltaValue = function(d) {
var value = trace.delta.relative ? d.relativeDelta : d.delta;
return value;
};
var deltaFormatText = function(value, numberFmt) {
if(value === 0 || typeof value !== 'number' || isNaN(value)) return '-';
return (value > 0 ? trace.delta.increasing.symbol : trace.delta.decreasing.symbol) + numberFmt(value);
};
var deltaFill = function(d) {
return d.delta >= 0 ? trace.delta.increasing.color : trace.delta.decreasing.color;
};
if(trace._deltaLastValue === undefined) {
trace._deltaLastValue = deltaValue(cd[0]);
}
var delta = numbers.select('text.delta');
delta
.call(Drawing.font, trace.delta.font)
.call(Color.fill, deltaFill({delta: trace._deltaLastValue}));
function writeDelta() {
delta.text(deltaFormatText(deltaValue(cd[0]), deltaFmt))
.call(Color.fill, deltaFill(cd[0]))
.call(svgTextUtils.convertToTspans, gd);
}
if(hasTransition(transitionOpts)) {
delta
.transition()
.duration(transitionOpts.duration)
.ease(transitionOpts.easing)
.tween('text', function() {
var that = d3.select(this);
var to = deltaValue(cd[0]);
var from = trace._deltaLastValue;
var transitionFmt = transitionFormat(trace.delta.valueformat, deltaFmt, from, to);
var interpolator = interpolateNumber(from, to);
trace._deltaLastValue = to;
return function(t) {
that.text(deltaFormatText(interpolator(t), transitionFmt));
that.call(Color.fill, deltaFill({delta: interpolator(t)}));
};
})
.each('end', function() { writeDelta(); onComplete && onComplete(); })
.each('interrupt', function() { writeDelta(); onComplete && onComplete(); });
} else {
writeDelta();
}
deltabBox = measureText(deltaFormatText(deltaValue(cd[0]), deltaFmt), trace.delta.font, numbersAnchor, gd);
return delta;
}
var key = trace.mode + trace.align;
var delta;
if(trace._hasDelta) {
delta = drawDelta();
key += trace.delta.position + trace.delta.font.size + trace.delta.font.family + trace.delta.valueformat;
key += trace.delta.increasing.symbol + trace.delta.decreasing.symbol;
numbersbBox = deltabBox;
}
if(trace._hasNumber) {
drawBignumber();
key += trace.number.font.size + trace.number.font.family + trace.number.valueformat + trace.number.suffix + trace.number.prefix;
numbersbBox = bignumberbBox;
}
// Position delta relative to bignumber
if(trace._hasDelta && trace._hasNumber) {
var bignumberCenter = [
(bignumberbBox.left + bignumberbBox.right) / 2,
(bignumberbBox.top + bignumberbBox.bottom) / 2
];
var deltaCenter = [
(deltabBox.left + deltabBox.right) / 2,
(deltabBox.top + deltabBox.bottom) / 2
];
var dx, dy;
var padding = 0.75 * trace.delta.font.size;
if(trace.delta.position === 'left') {
dx = cache(trace, 'deltaPos', 0, -1 * (bignumberbBox.width * (position[trace.align]) + deltabBox.width * (1 - position[trace.align]) + padding), key, Math.min);
dy = bignumberCenter[1] - deltaCenter[1];
numbersbBox = {
width: bignumberbBox.width + deltabBox.width + padding,
height: Math.max(bignumberbBox.height, deltabBox.height),
left: deltabBox.left + dx,
right: bignumberbBox.right,
top: Math.min(bignumberbBox.top, deltabBox.top + dy),
bottom: Math.max(bignumberbBox.bottom, deltabBox.bottom + dy)
};
}
if(trace.delta.position === 'right') {
dx = cache(trace, 'deltaPos', 0, bignumberbBox.width * (1 - position[trace.align]) + deltabBox.width * position[trace.align] + padding, key, Math.max);
dy = bignumberCenter[1] - deltaCenter[1];
numbersbBox = {
width: bignumberbBox.width + deltabBox.width + padding,
height: Math.max(bignumberbBox.height, deltabBox.height),
left: bignumberbBox.left,
right: deltabBox.right + dx,
top: Math.min(bignumberbBox.top, deltabBox.top + dy),
bottom: Math.max(bignumberbBox.bottom, deltabBox.bottom + dy)
};
}
if(trace.delta.position === 'bottom') {
dx = null;
dy = deltabBox.height;
numbersbBox = {
width: Math.max(bignumberbBox.width, deltabBox.width),
height: bignumberbBox.height + deltabBox.height,
left: Math.min(bignumberbBox.left, deltabBox.left),
right: Math.max(bignumberbBox.right, deltabBox.right),
top: bignumberbBox.bottom - bignumberbBox.height,
bottom: bignumberbBox.bottom + deltabBox.height
};
}
if(trace.delta.position === 'top') {
dx = null;
dy = bignumberbBox.top;
numbersbBox = {
width: Math.max(bignumberbBox.width, deltabBox.width),
height: bignumberbBox.height + deltabBox.height,
left: Math.min(bignumberbBox.left, deltabBox.left),
right: Math.max(bignumberbBox.right, deltabBox.right),
top: bignumberbBox.bottom - bignumberbBox.height - deltabBox.height,
bottom: bignumberbBox.bottom
};
}
delta.attr({dx: dx, dy: dy});
}
// Resize numbers to fit within space and position
if(trace._hasNumber || trace._hasDelta) {
numbers.attr('transform', function() {
var m = opts.numbersScaler(numbersbBox);
key += m[2];
var scaleRatio = cache(trace, 'numbersScale', 1, m[0], key, Math.min);
var translateY;
if(!trace._scaleNumbers) scaleRatio = 1;
if(trace._isAngular) {
// align vertically to bottom
translateY = numbersY - scaleRatio * numbersbBox.bottom;
} else {
// align vertically to center
translateY = numbersY - scaleRatio * (numbersbBox.top + numbersbBox.bottom) / 2;
}
// Stash the top position of numbersbBox for title positioning
trace._numbersTop = scaleRatio * (numbersbBox.top) + translateY;
var ref = numbersbBox[numbersAlign];
if(numbersAlign === 'center') ref = (numbersbBox.left + numbersbBox.right) / 2;
var translateX = numbersX - scaleRatio * ref;
// Stash translateX
translateX = cache(trace, 'numbersTranslate', 0, translateX, key, Math.max);
return strTranslate(translateX, translateY) + strScale(scaleRatio);
});
}
}
// Apply fill, stroke, stroke-width to SVG shape
function styleShape(p) {
p
.each(function(d) { Color.stroke(d3.select(this), d.line.color);})
.each(function(d) { Color.fill(d3.select(this), d.color);})
.style('stroke-width', function(d) { return d.line.width;});
}
// Returns a tween for a transition’s "d" attribute, transitioning any selected
// arcs from their current angle to the specified new angle.
function arcTween(arc, endAngle, newAngle) {
return function() {
var interp = interpolate(endAngle, newAngle);
return function(t) {
return arc.endAngle(interp(t))();
};
};
}
// mocks our axis
function mockAxis(gd, opts, zrange) {
var fullLayout = gd._fullLayout;
var axisIn = Lib.extendFlat({
type: 'linear',
ticks: 'outside',
range: zrange,
showline: true
}, opts);
var axisOut = {
type: 'linear',
_id: 'x' + opts._id
};
var axisOptions = {
letter: 'x',
font: fullLayout.font,
noHover: true,
noTickson: true
};
function coerce(attr, dflt) {
return Lib.coerce(axisIn, axisOut, axisLayoutAttrs, attr, dflt);
}
handleAxisDefaults(axisIn, axisOut, coerce, axisOptions, fullLayout);
handleAxisPositionDefaults(axisIn, axisOut, coerce, axisOptions);
return axisOut;
}
function fitTextInsideBox(textBB, width, height) {
// compute scaling ratio to have text fit within specified width and height
var ratio = Math.min(width / textBB.width, height / textBB.height);
return [ratio, textBB, width + 'x' + height];
}
function fitTextInsideCircle(textBB, radius) {
// compute scaling ratio to have text fit within specified radius
var elRadius = Math.sqrt((textBB.width / 2) * (textBB.width / 2) + textBB.height * textBB.height);
var ratio = radius / elRadius;
return [ratio, textBB, radius];
}
function measureText(txt, font, textAnchor, gd) {
var element = document.createElementNS('http://www.w3.org/2000/svg', 'text');
var sel = d3.select(element);
sel.text(txt)
.attr('x', 0)
.attr('y', 0)
.attr('text-anchor', textAnchor)
.attr('data-unformatted', txt)
.call(svgTextUtils.convertToTspans, gd)
.call(Drawing.font, font);
return Drawing.bBox(sel.node());
}
function cache(trace, name, initialValue, value, key, fn) {
var objName = '_cache' + name;
if(!(trace[objName] && trace[objName].key === key)) {
trace[objName] = {key: key, value: initialValue};
}
var v = Lib.aggNums(fn, null, [trace[objName].value, value], 2);
trace[objName].value = v;
return v;
}
},{"../../components/color":366,"../../components/drawing":388,"../../constants/alignment":471,"../../lib":503,"../../lib/svg_text_utils":529,"../../plots/cartesian/axes":554,"../../plots/cartesian/axis_defaults":556,"../../plots/cartesian/layout_attributes":569,"../../plots/cartesian/position_defaults":572,"./constants":857,"@plotly/d3":58,"d3-interpolate":116}],861:[function(_dereq_,module,exports){
'use strict';
var colorScaleAttrs = _dereq_('../../components/colorscale/attributes');
var axisHoverFormat = _dereq_('../../plots/cartesian/axis_format_attributes').axisHoverFormat;
var hovertemplateAttrs = _dereq_('../../plots/template_attributes').hovertemplateAttrs;
var meshAttrs = _dereq_('../mesh3d/attributes');
var baseAttrs = _dereq_('../../plots/attributes');
var extendFlat = _dereq_('../../lib/extend').extendFlat;
var overrideAll = _dereq_('../../plot_api/edit_types').overrideAll;
function makeSliceAttr(axLetter) {
return {
show: {
valType: 'boolean',
dflt: false,
},
locations: {
valType: 'data_array',
dflt: [],
},
fill: {
valType: 'number',
min: 0,
max: 1,
dflt: 1,
}
};
}
function makeCapAttr(axLetter) {
return {
show: {
valType: 'boolean',
dflt: true,
},
fill: {
valType: 'number',
min: 0,
max: 1,
dflt: 1,
}
};
}
var attrs = module.exports = overrideAll(extendFlat({
x: {
valType: 'data_array',
},
y: {
valType: 'data_array',
},
z: {
valType: 'data_array',
},
value: {
valType: 'data_array',
},
isomin: {
valType: 'number',
},
isomax: {
valType: 'number',
},
surface: {
show: {
valType: 'boolean',
dflt: true,
},
count: {
valType: 'integer',
dflt: 2,
min: 1,
},
fill: {
valType: 'number',
min: 0,
max: 1,
dflt: 1,
},
pattern: {
valType: 'flaglist',
flags: ['A', 'B', 'C', 'D', 'E'],
extras: ['all', 'odd', 'even'],
dflt: 'all',
}
},
spaceframe: {
show: {
valType: 'boolean',
dflt: false,
},
fill: {
valType: 'number',
min: 0,
max: 1,
dflt: 0.15,
}
},
slices: {
x: makeSliceAttr('x'),
y: makeSliceAttr('y'),
z: makeSliceAttr('z')
},
caps: {
x: makeCapAttr('x'),
y: makeCapAttr('y'),
z: makeCapAttr('z')
},
text: {
valType: 'string',
dflt: '',
arrayOk: true,
},
hovertext: {
valType: 'string',
dflt: '',
arrayOk: true,
},
hovertemplate: hovertemplateAttrs(),
xhoverformat: axisHoverFormat('x'),
yhoverformat: axisHoverFormat('y'),
zhoverformat: axisHoverFormat('z'),
valuehoverformat: axisHoverFormat('value', 1),
showlegend: extendFlat({}, baseAttrs.showlegend, {dflt: false})
},
colorScaleAttrs('', {
colorAttr: '`value`',
showScaleDflt: true,
editTypeOverride: 'calc'
}), {
opacity: meshAttrs.opacity,
lightposition: meshAttrs.lightposition,
lighting: meshAttrs.lighting,
flatshading: meshAttrs.flatshading,
contour: meshAttrs.contour,
hoverinfo: extendFlat({}, baseAttrs.hoverinfo)
}), 'calc', 'nested');
// required defaults to speed up surface normal calculations
attrs.flatshading.dflt = true; attrs.lighting.facenormalsepsilon.dflt = 0;
attrs.x.editType = attrs.y.editType = attrs.z.editType = attrs.value.editType = 'calc+clearAxisTypes';
attrs.transforms = undefined;
},{"../../components/colorscale/attributes":373,"../../lib/extend":493,"../../plot_api/edit_types":536,"../../plots/attributes":550,"../../plots/cartesian/axis_format_attributes":557,"../../plots/template_attributes":633,"../mesh3d/attributes":866}],862:[function(_dereq_,module,exports){
'use strict';
var colorscaleCalc = _dereq_('../../components/colorscale/calc');
var processGrid = _dereq_('../streamtube/calc').processGrid;
var filter = _dereq_('../streamtube/calc').filter;
module.exports = function calc(gd, trace) {
trace._len = Math.min(
trace.x.length,
trace.y.length,
trace.z.length,
trace.value.length
);
trace._x = filter(trace.x, trace._len);
trace._y = filter(trace.y, trace._len);
trace._z = filter(trace.z, trace._len);
trace._value = filter(trace.value, trace._len);
var grid = processGrid(trace);
trace._gridFill = grid.fill;
trace._Xs = grid.Xs;
trace._Ys = grid.Ys;
trace._Zs = grid.Zs;
trace._len = grid.len;
var min = Infinity;
var max = -Infinity;
for(var i = 0; i < trace._len; i++) {
var v = trace._value[i];
min = Math.min(min, v);
max = Math.max(max, v);
}
trace._minValues = min;
trace._maxValues = max;
trace._vMin = (trace.isomin === undefined || trace.isomin === null) ? min : trace.isomin;
trace._vMax = (trace.isomax === undefined || trace.isomin === null) ? max : trace.isomax;
colorscaleCalc(gd, trace, {
vals: [trace._vMin, trace._vMax],
containerStr: '',
cLetter: 'c'
});
};
},{"../../components/colorscale/calc":374,"../streamtube/calc":1040}],863:[function(_dereq_,module,exports){
'use strict';
var createMesh = _dereq_('../../../stackgl_modules').gl_mesh3d;
var parseColorScale = _dereq_('../../lib/gl_format_color').parseColorScale;
var str2RgbaArray = _dereq_('../../lib/str2rgbarray');
var extractOpts = _dereq_('../../components/colorscale').extractOpts;
var zip3 = _dereq_('../../plots/gl3d/zip3');
var findNearestOnAxis = function(w, arr) {
for(var q = arr.length - 1; q > 0; q--) {
var min = Math.min(arr[q], arr[q - 1]);
var max = Math.max(arr[q], arr[q - 1]);
if(max > min && min < w && w <= max) {
return {
id: q,
distRatio: (max - w) / (max - min)
};
}
}
return {
id: 0,
distRatio: 0
};
};
function IsosurfaceTrace(scene, mesh, uid) {
this.scene = scene;
this.uid = uid;
this.mesh = mesh;
this.name = '';
this.data = null;
this.showContour = false;
}
var proto = IsosurfaceTrace.prototype;
proto.handlePick = function(selection) {
if(selection.object === this.mesh) {
var rawId = selection.data.index;
var x = this.data._meshX[rawId];
var y = this.data._meshY[rawId];
var z = this.data._meshZ[rawId];
var height = this.data._Ys.length;
var depth = this.data._Zs.length;
var i = findNearestOnAxis(x, this.data._Xs).id;
var j = findNearestOnAxis(y, this.data._Ys).id;
var k = findNearestOnAxis(z, this.data._Zs).id;
var selectIndex = selection.index = k + depth * j + depth * height * i;
selection.traceCoordinate = [
this.data._meshX[selectIndex],
this.data._meshY[selectIndex],
this.data._meshZ[selectIndex],
this.data._value[selectIndex]
];
var text = this.data.hovertext || this.data.text;
if(Array.isArray(text) && text[selectIndex] !== undefined) {
selection.textLabel = text[selectIndex];
} else if(text) {
selection.textLabel = text;
}
return true;
}
};
proto.update = function(data) {
var scene = this.scene;
var layout = scene.fullSceneLayout;
this.data = generateIsoMeshes(data);
// Unpack position data
function toDataCoords(axis, coord, scale, calendar) {
return coord.map(function(x) {
return axis.d2l(x, 0, calendar) * scale;
});
}
var positions = zip3(
toDataCoords(layout.xaxis, data._meshX, scene.dataScale[0], data.xcalendar),
toDataCoords(layout.yaxis, data._meshY, scene.dataScale[1], data.ycalendar),
toDataCoords(layout.zaxis, data._meshZ, scene.dataScale[2], data.zcalendar));
var cells = zip3(data._meshI, data._meshJ, data._meshK);
var config = {
positions: positions,
cells: cells,
lightPosition: [data.lightposition.x, data.lightposition.y, data.lightposition.z],
ambient: data.lighting.ambient,
diffuse: data.lighting.diffuse,
specular: data.lighting.specular,
roughness: data.lighting.roughness,
fresnel: data.lighting.fresnel,
vertexNormalsEpsilon: data.lighting.vertexnormalsepsilon,
faceNormalsEpsilon: data.lighting.facenormalsepsilon,
opacity: data.opacity,
contourEnable: data.contour.show,
contourColor: str2RgbaArray(data.contour.color).slice(0, 3),
contourWidth: data.contour.width,
useFacetNormals: data.flatshading
};
var cOpts = extractOpts(data);
config.vertexIntensity = data._meshIntensity;
config.vertexIntensityBounds = [cOpts.min, cOpts.max];
config.colormap = parseColorScale(data);
// Update mesh
this.mesh.update(config);
};
proto.dispose = function() {
this.scene.glplot.remove(this.mesh);
this.mesh.dispose();
};
var GRID_TYPES = ['xyz', 'xzy', 'yxz', 'yzx', 'zxy', 'zyx'];
function generateIsoMeshes(data) {
data._meshI = [];
data._meshJ = [];
data._meshK = [];
var showSurface = data.surface.show;
var showSpaceframe = data.spaceframe.show;
var surfaceFill = data.surface.fill;
var spaceframeFill = data.spaceframe.fill;
var drawingSurface = false;
var drawingSpaceframe = false;
var numFaces = 0;
var numVertices;
var beginVertextLength;
var Xs = data._Xs;
var Ys = data._Ys;
var Zs = data._Zs;
var width = Xs.length;
var height = Ys.length;
var depth = Zs.length;
var filled = GRID_TYPES.indexOf(data._gridFill.replace(/-/g, '').replace(/\+/g, ''));
var getIndex = function(i, j, k) {
switch(filled) {
case 5: // 'zyx'
return k + depth * j + depth * height * i;
case 4: // 'zxy'
return k + depth * i + depth * width * j;
case 3: // 'yzx'
return j + height * k + height * depth * i;
case 2: // 'yxz'
return j + height * i + height * width * k;
case 1: // 'xzy'
return i + width * k + width * depth * j;
default: // case 0: // 'xyz'
return i + width * j + width * height * k;
}
};
var minValues = data._minValues;
var maxValues = data._maxValues;
var vMin = data._vMin;
var vMax = data._vMax;
var allXs;
var allYs;
var allZs;
var allVs;
function findVertexId(x, y, z) {
// could be used to find the vertex id of previously generated vertex within the group
var len = allVs.length;
for(var f = beginVertextLength; f < len; f++) {
if(
x === allXs[f] &&
y === allYs[f] &&
z === allZs[f]
) {
return f;
}
}
return -1;
}
function beginGroup() {
beginVertextLength = numVertices;
}
function emptyVertices() {
allXs = [];
allYs = [];
allZs = [];
allVs = [];
numVertices = 0;
beginGroup();
}
function addVertex(x, y, z, v) {
allXs.push(x);
allYs.push(y);
allZs.push(z);
allVs.push(v);
numVertices++;
return numVertices - 1;
}
function addFace(a, b, c) {
data._meshI.push(a);
data._meshJ.push(b);
data._meshK.push(c);
numFaces++;
return numFaces - 1;
}
function getCenter(A, B, C) {
var M = [];
for(var i = 0; i < A.length; i++) {
M[i] = (A[i] + B[i] + C[i]) / 3.0;
}
return M;
}
function getBetween(A, B, r) {
var M = [];
for(var i = 0; i < A.length; i++) {
M[i] = A[i] * (1 - r) + r * B[i];
}
return M;
}
var activeFill;
function setFill(fill) {
activeFill = fill;
}
function createOpenTri(xyzv, abc) {
var A = xyzv[0];
var B = xyzv[1];
var C = xyzv[2];
var G = getCenter(A, B, C);
var r = Math.sqrt(1 - activeFill);
var p1 = getBetween(G, A, r);
var p2 = getBetween(G, B, r);
var p3 = getBetween(G, C, r);
var a = abc[0];
var b = abc[1];
var c = abc[2];
return {
xyzv: [
[A, B, p2], [p2, p1, A],
[B, C, p3], [p3, p2, B],
[C, A, p1], [p1, p3, C]
],
abc: [
[a, b, -1], [-1, -1, a],
[b, c, -1], [-1, -1, b],
[c, a, -1], [-1, -1, c]
]
};
}
function styleIncludes(style, char) {
if(style === 'all' || style === null) return true;
return (style.indexOf(char) > -1);
}
function mapValue(style, value) {
if(style === null) return value;
return style;
}
function drawTri(style, xyzv, abc) {
beginGroup();
var allXYZVs = [xyzv];
var allABCs = [abc];
if(activeFill >= 1) {
allXYZVs = [xyzv];
allABCs = [abc];
} else if(activeFill > 0) {
var openTri = createOpenTri(xyzv, abc);
allXYZVs = openTri.xyzv;
allABCs = openTri.abc;
}
for(var f = 0; f < allXYZVs.length; f++) {
xyzv = allXYZVs[f];
abc = allABCs[f];
var pnts = [];
for(var i = 0; i < 3; i++) {
var x = xyzv[i][0];
var y = xyzv[i][1];
var z = xyzv[i][2];
var v = xyzv[i][3];
var id = (abc[i] > -1) ? abc[i] : findVertexId(x, y, z);
if(id > -1) {
pnts[i] = id;
} else {
pnts[i] = addVertex(x, y, z, mapValue(style, v));
}
}
addFace(pnts[0], pnts[1], pnts[2]);
}
}
function drawQuad(style, xyzv, abcd) {
var makeTri = function(i, j, k) {
drawTri(style, [xyzv[i], xyzv[j], xyzv[k]], [abcd[i], abcd[j], abcd[k]]);
};
makeTri(0, 1, 2);
makeTri(2, 3, 0);
}
function drawTetra(style, xyzv, abcd) {
var makeTri = function(i, j, k) {
drawTri(style, [xyzv[i], xyzv[j], xyzv[k]], [abcd[i], abcd[j], abcd[k]]);
};
makeTri(0, 1, 2);
makeTri(3, 0, 1);
makeTri(2, 3, 0);
makeTri(1, 2, 3);
}
function calcIntersection(pointOut, pointIn, min, max) {
var value = pointOut[3];
if(value < min) value = min;
if(value > max) value = max;
var ratio = (pointOut[3] - value) / (pointOut[3] - pointIn[3] + 0.000000001); // we had to add this error to force solve the tiny caps
var result = [];
for(var s = 0; s < 4; s++) {
result[s] = (1 - ratio) * pointOut[s] + ratio * pointIn[s];
}
return result;
}
function inRange(value, min, max) {
return (
value >= min &&
value <= max
);
}
function almostInFinalRange(value) {
var vErr = 0.001 * (vMax - vMin);
return (
value >= vMin - vErr &&
value <= vMax + vErr
);
}
function getXYZV(indecies) {
var xyzv = [];
for(var q = 0; q < 4; q++) {
var index = indecies[q];
xyzv.push(
[
data._x[index],
data._y[index],
data._z[index],
data._value[index]
]
);
}
return xyzv;
}
var MAX_PASS = 3;
function tryCreateTri(style, xyzv, abc, min, max, nPass) {
if(!nPass) nPass = 1;
abc = [-1, -1, -1]; // Note: for the moment we override indices
// to run faster! But it is possible to comment this line
// to reduce the number of vertices.
var result = false;
var ok = [
inRange(xyzv[0][3], min, max),
inRange(xyzv[1][3], min, max),
inRange(xyzv[2][3], min, max)
];
if(!ok[0] && !ok[1] && !ok[2]) {
return false;
}
var tryDrawTri = function(style, xyzv, abc) {
if( // we check here if the points are in `real` iso-min/max range
almostInFinalRange(xyzv[0][3]) &&
almostInFinalRange(xyzv[1][3]) &&
almostInFinalRange(xyzv[2][3])
) {
drawTri(style, xyzv, abc);
return true;
} else if(nPass < MAX_PASS) {
return tryCreateTri(style, xyzv, abc, vMin, vMax, ++nPass); // i.e. second pass using actual vMin vMax bounds
}
return false;
};
if(ok[0] && ok[1] && ok[2]) {
return tryDrawTri(style, xyzv, abc) || result;
}
var interpolated = false;
[
[0, 1, 2],
[2, 0, 1],
[1, 2, 0]
].forEach(function(e) {
if(ok[e[0]] && ok[e[1]] && !ok[e[2]]) {
var A = xyzv[e[0]];
var B = xyzv[e[1]];
var C = xyzv[e[2]];
var p1 = calcIntersection(C, A, min, max);
var p2 = calcIntersection(C, B, min, max);
result = tryDrawTri(style, [p2, p1, A], [-1, -1, abc[e[0]]]) || result;
result = tryDrawTri(style, [A, B, p2], [abc[e[0]], abc[e[1]], -1]) || result;
interpolated = true;
}
});
if(interpolated) return result;
[
[0, 1, 2],
[1, 2, 0],
[2, 0, 1]
].forEach(function(e) {
if(ok[e[0]] && !ok[e[1]] && !ok[e[2]]) {
var A = xyzv[e[0]];
var B = xyzv[e[1]];
var C = xyzv[e[2]];
var p1 = calcIntersection(B, A, min, max);
var p2 = calcIntersection(C, A, min, max);
result = tryDrawTri(style, [p2, p1, A], [-1, -1, abc[e[0]]]) || result;
interpolated = true;
}
});
return result;
}
function tryCreateTetra(style, abcd, min, max) {
var result = false;
var xyzv = getXYZV(abcd);
var ok = [
inRange(xyzv[0][3], min, max),
inRange(xyzv[1][3], min, max),
inRange(xyzv[2][3], min, max),
inRange(xyzv[3][3], min, max)
];
if(!ok[0] && !ok[1] && !ok[2] && !ok[3]) {
return result;
}
if(ok[0] && ok[1] && ok[2] && ok[3]) {
if(drawingSpaceframe) {
result = drawTetra(style, xyzv, abcd) || result;
}
return result;
}
var interpolated = false;
[
[0, 1, 2, 3],
[3, 0, 1, 2],
[2, 3, 0, 1],
[1, 2, 3, 0]
].forEach(function(e) {
if(ok[e[0]] && ok[e[1]] && ok[e[2]] && !ok[e[3]]) {
var A = xyzv[e[0]];
var B = xyzv[e[1]];
var C = xyzv[e[2]];
var D = xyzv[e[3]];
if(drawingSpaceframe) {
result = drawTri(style, [A, B, C], [abcd[e[0]], abcd[e[1]], abcd[e[2]]]) || result;
} else {
var p1 = calcIntersection(D, A, min, max);
var p2 = calcIntersection(D, B, min, max);
var p3 = calcIntersection(D, C, min, max);
result = drawTri(null, [p1, p2, p3], [-1, -1, -1]) || result;
}
interpolated = true;
}
});
if(interpolated) return result;
[
[0, 1, 2, 3],
[1, 2, 3, 0],
[2, 3, 0, 1],
[3, 0, 1, 2],
[0, 2, 3, 1],
[1, 3, 2, 0]
].forEach(function(e) {
if(ok[e[0]] && ok[e[1]] && !ok[e[2]] && !ok[e[3]]) {
var A = xyzv[e[0]];
var B = xyzv[e[1]];
var C = xyzv[e[2]];
var D = xyzv[e[3]];
var p1 = calcIntersection(C, A, min, max);
var p2 = calcIntersection(C, B, min, max);
var p3 = calcIntersection(D, B, min, max);
var p4 = calcIntersection(D, A, min, max);
if(drawingSpaceframe) {
result = drawTri(style, [A, p4, p1], [abcd[e[0]], -1, -1]) || result;
result = drawTri(style, [B, p2, p3], [abcd[e[1]], -1, -1]) || result;
} else {
result = drawQuad(null, [p1, p2, p3, p4], [-1, -1, -1, -1]) || result;
}
interpolated = true;
}
});
if(interpolated) return result;
[
[0, 1, 2, 3],
[1, 2, 3, 0],
[2, 3, 0, 1],
[3, 0, 1, 2]
].forEach(function(e) {
if(ok[e[0]] && !ok[e[1]] && !ok[e[2]] && !ok[e[3]]) {
var A = xyzv[e[0]];
var B = xyzv[e[1]];
var C = xyzv[e[2]];
var D = xyzv[e[3]];
var p1 = calcIntersection(B, A, min, max);
var p2 = calcIntersection(C, A, min, max);
var p3 = calcIntersection(D, A, min, max);
if(drawingSpaceframe) {
result = drawTri(style, [A, p1, p2], [abcd[e[0]], -1, -1]) || result;
result = drawTri(style, [A, p2, p3], [abcd[e[0]], -1, -1]) || result;
result = drawTri(style, [A, p3, p1], [abcd[e[0]], -1, -1]) || result;
} else {
result = drawTri(null, [p1, p2, p3], [-1, -1, -1]) || result;
}
interpolated = true;
}
});
return result;
}
function addCube(style, p000, p001, p010, p011, p100, p101, p110, p111, min, max) {
var result = false;
if(drawingSurface) {
if(styleIncludes(style, 'A')) {
result = tryCreateTetra(null, [p000, p001, p010, p100], min, max) || result;
}
if(styleIncludes(style, 'B')) {
result = tryCreateTetra(null, [p001, p010, p011, p111], min, max) || result;
}
if(styleIncludes(style, 'C')) {
result = tryCreateTetra(null, [p001, p100, p101, p111], min, max) || result;
}
if(styleIncludes(style, 'D')) {
result = tryCreateTetra(null, [p010, p100, p110, p111], min, max) || result;
}
if(styleIncludes(style, 'E')) {
result = tryCreateTetra(null, [p001, p010, p100, p111], min, max) || result;
}
}
if(drawingSpaceframe) {
result = tryCreateTetra(style, [p001, p010, p100, p111], min, max) || result;
}
return result;
}
function addRect(style, a, b, c, d, min, max, previousResult) {
return [
(previousResult[0] === true) ? true :
tryCreateTri(style, getXYZV([a, b, c]), [a, b, c], min, max),
(previousResult[1] === true) ? true :
tryCreateTri(style, getXYZV([c, d, a]), [c, d, a], min, max)
];
}
function begin2dCell(style, p00, p01, p10, p11, min, max, isEven, previousResult) {
// used to create caps and/or slices on exact axis points
if(isEven) {
return addRect(style, p00, p01, p11, p10, min, max, previousResult);
} else {
return addRect(style, p01, p11, p10, p00, min, max, previousResult);
}
}
function beginSection(style, i, j, k, min, max, distRatios) {
// used to create slices between axis points
var result = false;
var A, B, C, D;
var makeSection = function() {
result = tryCreateTri(style, [A, B, C], [-1, -1, -1], min, max) || result;
result = tryCreateTri(style, [C, D, A], [-1, -1, -1], min, max) || result;
};
var rX = distRatios[0];
var rY = distRatios[1];
var rZ = distRatios[2];
if(rX) {
A = getBetween(getXYZV([getIndex(i, j - 0, k - 0)])[0], getXYZV([getIndex(i - 1, j - 0, k - 0)])[0], rX);
B = getBetween(getXYZV([getIndex(i, j - 0, k - 1)])[0], getXYZV([getIndex(i - 1, j - 0, k - 1)])[0], rX);
C = getBetween(getXYZV([getIndex(i, j - 1, k - 1)])[0], getXYZV([getIndex(i - 1, j - 1, k - 1)])[0], rX);
D = getBetween(getXYZV([getIndex(i, j - 1, k - 0)])[0], getXYZV([getIndex(i - 1, j - 1, k - 0)])[0], rX);
makeSection();
}
if(rY) {
A = getBetween(getXYZV([getIndex(i - 0, j, k - 0)])[0], getXYZV([getIndex(i - 0, j - 1, k - 0)])[0], rY);
B = getBetween(getXYZV([getIndex(i - 0, j, k - 1)])[0], getXYZV([getIndex(i - 0, j - 1, k - 1)])[0], rY);
C = getBetween(getXYZV([getIndex(i - 1, j, k - 1)])[0], getXYZV([getIndex(i - 1, j - 1, k - 1)])[0], rY);
D = getBetween(getXYZV([getIndex(i - 1, j, k - 0)])[0], getXYZV([getIndex(i - 1, j - 1, k - 0)])[0], rY);
makeSection();
}
if(rZ) {
A = getBetween(getXYZV([getIndex(i - 0, j - 0, k)])[0], getXYZV([getIndex(i - 0, j - 0, k - 1)])[0], rZ);
B = getBetween(getXYZV([getIndex(i - 0, j - 1, k)])[0], getXYZV([getIndex(i - 0, j - 1, k - 1)])[0], rZ);
C = getBetween(getXYZV([getIndex(i - 1, j - 1, k)])[0], getXYZV([getIndex(i - 1, j - 1, k - 1)])[0], rZ);
D = getBetween(getXYZV([getIndex(i - 1, j - 0, k)])[0], getXYZV([getIndex(i - 1, j - 0, k - 1)])[0], rZ);
makeSection();
}
return result;
}
function begin3dCell(style, p000, p001, p010, p011, p100, p101, p110, p111, min, max, isEven) {
// used to create spaceframe and/or iso-surfaces
var cellStyle = style;
if(isEven) {
if(drawingSurface && style === 'even') cellStyle = null;
return addCube(cellStyle, p000, p001, p010, p011, p100, p101, p110, p111, min, max);
} else {
if(drawingSurface && style === 'odd') cellStyle = null;
return addCube(cellStyle, p111, p110, p101, p100, p011, p010, p001, p000, min, max);
}
}
function draw2dX(style, items, min, max, previousResult) {
var result = [];
var n = 0;
for(var q = 0; q < items.length; q++) {
var i = items[q];
for(var k = 1; k < depth; k++) {
for(var j = 1; j < height; j++) {
result.push(
begin2dCell(style,
getIndex(i, j - 1, k - 1),
getIndex(i, j - 1, k),
getIndex(i, j, k - 1),
getIndex(i, j, k),
min,
max,
(i + j + k) % 2,
(previousResult && previousResult[n]) ? previousResult[n] : []
)
);
n++;
}
}
}
return result;
}
function draw2dY(style, items, min, max, previousResult) {
var result = [];
var n = 0;
for(var q = 0; q < items.length; q++) {
var j = items[q];
for(var i = 1; i < width; i++) {
for(var k = 1; k < depth; k++) {
result.push(
begin2dCell(style,
getIndex(i - 1, j, k - 1),
getIndex(i, j, k - 1),
getIndex(i - 1, j, k),
getIndex(i, j, k),
min,
max,
(i + j + k) % 2,
(previousResult && previousResult[n]) ? previousResult[n] : []
)
);
n++;
}
}
}
return result;
}
function draw2dZ(style, items, min, max, previousResult) {
var result = [];
var n = 0;
for(var q = 0; q < items.length; q++) {
var k = items[q];
for(var j = 1; j < height; j++) {
for(var i = 1; i < width; i++) {
result.push(
begin2dCell(style,
getIndex(i - 1, j - 1, k),
getIndex(i - 1, j, k),
getIndex(i, j - 1, k),
getIndex(i, j, k),
min,
max,
(i + j + k) % 2,
(previousResult && previousResult[n]) ? previousResult[n] : []
)
);
n++;
}
}
}
return result;
}
function draw3d(style, min, max) {
for(var k = 1; k < depth; k++) {
for(var j = 1; j < height; j++) {
for(var i = 1; i < width; i++) {
begin3dCell(style,
getIndex(i - 1, j - 1, k - 1),
getIndex(i - 1, j - 1, k),
getIndex(i - 1, j, k - 1),
getIndex(i - 1, j, k),
getIndex(i, j - 1, k - 1),
getIndex(i, j - 1, k),
getIndex(i, j, k - 1),
getIndex(i, j, k),
min,
max,
(i + j + k) % 2
);
}
}
}
}
function drawSpaceframe(style, min, max) {
drawingSpaceframe = true;
draw3d(style, min, max);
drawingSpaceframe = false;
}
function drawSurface(style, min, max) {
drawingSurface = true;
draw3d(style, min, max);
drawingSurface = false;
}
function drawSectionX(style, items, min, max, distRatios, previousResult) {
var result = [];
var n = 0;
for(var q = 0; q < items.length; q++) {
var i = items[q];
for(var k = 1; k < depth; k++) {
for(var j = 1; j < height; j++) {
result.push(
beginSection(style, i, j, k, min, max, distRatios[q],
(previousResult && previousResult[n]) ? previousResult[n] : []
)
);
n++;
}
}
}
return result;
}
function drawSectionY(style, items, min, max, distRatios, previousResult) {
var result = [];
var n = 0;
for(var q = 0; q < items.length; q++) {
var j = items[q];
for(var i = 1; i < width; i++) {
for(var k = 1; k < depth; k++) {
result.push(
beginSection(style, i, j, k, min, max, distRatios[q],
(previousResult && previousResult[n]) ? previousResult[n] : []
)
);
n++;
}
}
}
return result;
}
function drawSectionZ(style, items, min, max, distRatios, previousResult) {
var result = [];
var n = 0;
for(var q = 0; q < items.length; q++) {
var k = items[q];
for(var j = 1; j < height; j++) {
for(var i = 1; i < width; i++) {
result.push(
beginSection(style, i, j, k, min, max, distRatios[q],
(previousResult && previousResult[n]) ? previousResult[n] : []
)
);
n++;
}
}
}
return result;
}
function createRange(a, b) {
var range = [];
for(var q = a; q < b; q++) {
range.push(q);
}
return range;
}
function insertGridPoints() {
for(var i = 0; i < width; i++) {
for(var j = 0; j < height; j++) {
for(var k = 0; k < depth; k++) {
var index = getIndex(i, j, k);
addVertex(
data._x[index],
data._y[index],
data._z[index],
data._value[index]
);
}
}
}
}
function drawAll() {
emptyVertices();
// insert grid points
insertGridPoints();
var activeStyle = null;
// draw spaceframes
if(showSpaceframe && spaceframeFill) {
setFill(spaceframeFill);
drawSpaceframe(activeStyle, vMin, vMax);
}
// draw iso-surfaces
if(showSurface && surfaceFill) {
setFill(surfaceFill);
var surfacePattern = data.surface.pattern;
var surfaceCount = data.surface.count;
for(var q = 0; q < surfaceCount; q++) {
var ratio = (surfaceCount === 1) ? 0.5 : q / (surfaceCount - 1);
var level = (1 - ratio) * vMin + ratio * vMax;
var d1 = Math.abs(level - minValues);
var d2 = Math.abs(level - maxValues);
var ranges = (d1 > d2) ?
[minValues, level] :
[level, maxValues];
drawSurface(surfacePattern, ranges[0], ranges[1]);
}
}
var setupMinMax = [
[ Math.min(vMin, maxValues), Math.max(vMin, maxValues) ],
[ Math.min(minValues, vMax), Math.max(minValues, vMax) ]
];
['x', 'y', 'z'].forEach(function(e) {
var preRes = [];
for(var s = 0; s < setupMinMax.length; s++) {
var count = 0;
var activeMin = setupMinMax[s][0];
var activeMax = setupMinMax[s][1];
// draw slices
var slice = data.slices[e];
if(slice.show && slice.fill) {
setFill(slice.fill);
var exactIndices = [];
var ceilIndices = [];
var distRatios = [];
if(slice.locations.length) {
for(var q = 0; q < slice.locations.length; q++) {
var near = findNearestOnAxis(
slice.locations[q],
(e === 'x') ? Xs :
(e === 'y') ? Ys : Zs
);
if(near.distRatio === 0) {
exactIndices.push(near.id);
} else if(near.id > 0) {
ceilIndices.push(near.id);
if(e === 'x') {
distRatios.push([near.distRatio, 0, 0]);
} else if(e === 'y') {
distRatios.push([0, near.distRatio, 0]);
} else {
distRatios.push([0, 0, near.distRatio]);
}
}
}
} else {
if(e === 'x') {
exactIndices = createRange(1, width - 1);
} else if(e === 'y') {
exactIndices = createRange(1, height - 1);
} else {
exactIndices = createRange(1, depth - 1);
}
}
if(ceilIndices.length > 0) {
if(e === 'x') {
preRes[count] = drawSectionX(activeStyle, ceilIndices, activeMin, activeMax, distRatios, preRes[count]);
} else if(e === 'y') {
preRes[count] = drawSectionY(activeStyle, ceilIndices, activeMin, activeMax, distRatios, preRes[count]);
} else {
preRes[count] = drawSectionZ(activeStyle, ceilIndices, activeMin, activeMax, distRatios, preRes[count]);
}
count++;
}
if(exactIndices.length > 0) {
if(e === 'x') {
preRes[count] = draw2dX(activeStyle, exactIndices, activeMin, activeMax, preRes[count]);
} else if(e === 'y') {
preRes[count] = draw2dY(activeStyle, exactIndices, activeMin, activeMax, preRes[count]);
} else {
preRes[count] = draw2dZ(activeStyle, exactIndices, activeMin, activeMax, preRes[count]);
}
count++;
}
}
// draw caps
var cap = data.caps[e];
if(cap.show && cap.fill) {
setFill(cap.fill);
if(e === 'x') {
preRes[count] = draw2dX(activeStyle, [0, width - 1], activeMin, activeMax, preRes[count]);
} else if(e === 'y') {
preRes[count] = draw2dY(activeStyle, [0, height - 1], activeMin, activeMax, preRes[count]);
} else {
preRes[count] = draw2dZ(activeStyle, [0, depth - 1], activeMin, activeMax, preRes[count]);
}
count++;
}
}
});
// remove vertices arrays (i.e. grid points) in case no face was created.
if(numFaces === 0) {
emptyVertices();
}
data._meshX = allXs;
data._meshY = allYs;
data._meshZ = allZs;
data._meshIntensity = allVs;
data._Xs = Xs;
data._Ys = Ys;
data._Zs = Zs;
}
drawAll();
return data;
}
function createIsosurfaceTrace(scene, data) {
var gl = scene.glplot.gl;
var mesh = createMesh({gl: gl});
var result = new IsosurfaceTrace(scene, mesh, data.uid);
mesh._trace = result;
result.update(data);
scene.glplot.add(mesh);
return result;
}
module.exports = {
findNearestOnAxis: findNearestOnAxis,
generateIsoMeshes: generateIsoMeshes,
createIsosurfaceTrace: createIsosurfaceTrace,
};
},{"../../../stackgl_modules":1119,"../../components/colorscale":378,"../../lib/gl_format_color":499,"../../lib/str2rgbarray":528,"../../plots/gl3d/zip3":609}],864:[function(_dereq_,module,exports){
'use strict';
var Lib = _dereq_('../../lib');
var Registry = _dereq_('../../registry');
var attributes = _dereq_('./attributes');
var colorscaleDefaults = _dereq_('../../components/colorscale/defaults');
function supplyDefaults(traceIn, traceOut, defaultColor, layout) {
function coerce(attr, dflt) {
return Lib.coerce(traceIn, traceOut, attributes, attr, dflt);
}
supplyIsoDefaults(traceIn, traceOut, defaultColor, layout, coerce);
}
function supplyIsoDefaults(traceIn, traceOut, defaultColor, layout, coerce) {
var isomin = coerce('isomin');
var isomax = coerce('isomax');
if(isomax !== undefined && isomax !== null &&
isomin !== undefined && isomin !== null &&
isomin > isomax) {
// applying default values in this case:
traceOut.isomin = null;
traceOut.isomax = null;
}
var x = coerce('x');
var y = coerce('y');
var z = coerce('z');
var value = coerce('value');
if(
!x || !x.length ||
!y || !y.length ||
!z || !z.length ||
!value || !value.length
) {
traceOut.visible = false;
return;
}
var handleCalendarDefaults = Registry.getComponentMethod('calendars', 'handleTraceDefaults');
handleCalendarDefaults(traceIn, traceOut, ['x', 'y', 'z'], layout);
coerce('valuehoverformat');
['x', 'y', 'z'].forEach(function(dim) {
coerce(dim + 'hoverformat');
var capDim = 'caps.' + dim;
var showCap = coerce(capDim + '.show');
if(showCap) {
coerce(capDim + '.fill');
}
var sliceDim = 'slices.' + dim;
var showSlice = coerce(sliceDim + '.show');
if(showSlice) {
coerce(sliceDim + '.fill');
coerce(sliceDim + '.locations');
}
});
var showSpaceframe = coerce('spaceframe.show');
if(showSpaceframe) {
coerce('spaceframe.fill');
}
var showSurface = coerce('surface.show');
if(showSurface) {
coerce('surface.count');
coerce('surface.fill');
coerce('surface.pattern');
}
var showContour = coerce('contour.show');
if(showContour) {
coerce('contour.color');
coerce('contour.width');
}
// Coerce remaining properties
[
'text',
'hovertext',
'hovertemplate',
'lighting.ambient',
'lighting.diffuse',
'lighting.specular',
'lighting.roughness',
'lighting.fresnel',
'lighting.vertexnormalsepsilon',
'lighting.facenormalsepsilon',
'lightposition.x',
'lightposition.y',
'lightposition.z',
'flatshading',
'opacity'
].forEach(function(x) { coerce(x); });
colorscaleDefaults(traceIn, traceOut, layout, coerce, {prefix: '', cLetter: 'c'});
// disable 1D transforms (for now)
traceOut._length = null;
}
module.exports = {
supplyDefaults: supplyDefaults,
supplyIsoDefaults: supplyIsoDefaults
};
},{"../../components/colorscale/defaults":376,"../../lib":503,"../../registry":638,"./attributes":861}],865:[function(_dereq_,module,exports){
'use strict';
module.exports = {
attributes: _dereq_('./attributes'),
supplyDefaults: _dereq_('./defaults').supplyDefaults,
calc: _dereq_('./calc'),
colorbar: {
min: 'cmin',
max: 'cmax'
},
plot: _dereq_('./convert').createIsosurfaceTrace,
moduleType: 'trace',
name: 'isosurface',
basePlotModule: _dereq_('../../plots/gl3d'),
categories: ['gl3d', 'showLegend'],
meta: {
}
};
},{"../../plots/gl3d":598,"./attributes":861,"./calc":862,"./convert":863,"./defaults":864}],866:[function(_dereq_,module,exports){
'use strict';
var colorScaleAttrs = _dereq_('../../components/colorscale/attributes');
var axisHoverFormat = _dereq_('../../plots/cartesian/axis_format_attributes').axisHoverFormat;
var hovertemplateAttrs = _dereq_('../../plots/template_attributes').hovertemplateAttrs;
var surfaceAttrs = _dereq_('../surface/attributes');
var baseAttrs = _dereq_('../../plots/attributes');
var extendFlat = _dereq_('../../lib/extend').extendFlat;
module.exports = extendFlat({
x: {
valType: 'data_array',
editType: 'calc+clearAxisTypes',
},
y: {
valType: 'data_array',
editType: 'calc+clearAxisTypes',
},
z: {
valType: 'data_array',
editType: 'calc+clearAxisTypes',
},
i: {
valType: 'data_array',
editType: 'calc',
},
j: {
valType: 'data_array',
editType: 'calc',
},
k: {
valType: 'data_array',
editType: 'calc',
},
text: {
valType: 'string',
dflt: '',
arrayOk: true,
editType: 'calc',
},
hovertext: {
valType: 'string',
dflt: '',
arrayOk: true,
editType: 'calc',
},
hovertemplate: hovertemplateAttrs({editType: 'calc'}),
xhoverformat: axisHoverFormat('x'),
yhoverformat: axisHoverFormat('y'),
zhoverformat: axisHoverFormat('z'),
delaunayaxis: {
valType: 'enumerated',
values: [ 'x', 'y', 'z' ],
dflt: 'z',
editType: 'calc',
},
alphahull: {
valType: 'number',
dflt: -1,
editType: 'calc',
},
intensity: {
valType: 'data_array',
editType: 'calc',
},
intensitymode: {
valType: 'enumerated',
values: ['vertex', 'cell'],
dflt: 'vertex',
editType: 'calc',
},
// Color field
color: {
valType: 'color',
editType: 'calc',
},
vertexcolor: {
valType: 'data_array',
editType: 'calc',
},
facecolor: {
valType: 'data_array',
editType: 'calc',
},
transforms: undefined
},
colorScaleAttrs('', {
colorAttr: '`intensity`',
showScaleDflt: true,
editTypeOverride: 'calc'
}), {
opacity: surfaceAttrs.opacity,
// Flat shaded mode
flatshading: {
valType: 'boolean',
dflt: false,
editType: 'calc',
},
contour: {
show: extendFlat({}, surfaceAttrs.contours.x.show, {
}),
color: surfaceAttrs.contours.x.color,
width: surfaceAttrs.contours.x.width,
editType: 'calc'
},
lightposition: {
x: extendFlat({}, surfaceAttrs.lightposition.x, {dflt: 1e5}),
y: extendFlat({}, surfaceAttrs.lightposition.y, {dflt: 1e5}),
z: extendFlat({}, surfaceAttrs.lightposition.z, {dflt: 0}),
editType: 'calc'
},
lighting: extendFlat({
vertexnormalsepsilon: {
valType: 'number',
min: 0.00,
max: 1,
dflt: 1e-12, // otherwise finely tessellated things eg. the brain will have no specular light reflection
editType: 'calc',
},
facenormalsepsilon: {
valType: 'number',
min: 0.00,
max: 1,
dflt: 1e-6, // even the brain model doesn't appear to need finer than this
editType: 'calc',
},
editType: 'calc'
}, surfaceAttrs.lighting),
hoverinfo: extendFlat({}, baseAttrs.hoverinfo, {editType: 'calc'}),
showlegend: extendFlat({}, baseAttrs.showlegend, {dflt: false})
});
},{"../../components/colorscale/attributes":373,"../../lib/extend":493,"../../plots/attributes":550,"../../plots/cartesian/axis_format_attributes":557,"../../plots/template_attributes":633,"../surface/attributes":1056}],867:[function(_dereq_,module,exports){
'use strict';
var colorscaleCalc = _dereq_('../../components/colorscale/calc');
module.exports = function calc(gd, trace) {
if(trace.intensity) {
colorscaleCalc(gd, trace, {
vals: trace.intensity,
containerStr: '',
cLetter: 'c'
});
}
};
},{"../../components/colorscale/calc":374}],868:[function(_dereq_,module,exports){
'use strict';
var createMesh = _dereq_('../../../stackgl_modules').gl_mesh3d;
var triangulate = _dereq_('../../../stackgl_modules').delaunay_triangulate;
var alphaShape = _dereq_('../../../stackgl_modules').alpha_shape;
var convexHull = _dereq_('../../../stackgl_modules').convex_hull;
var parseColorScale = _dereq_('../../lib/gl_format_color').parseColorScale;
var str2RgbaArray = _dereq_('../../lib/str2rgbarray');
var extractOpts = _dereq_('../../components/colorscale').extractOpts;
var zip3 = _dereq_('../../plots/gl3d/zip3');
function Mesh3DTrace(scene, mesh, uid) {
this.scene = scene;
this.uid = uid;
this.mesh = mesh;
this.name = '';
this.color = '#fff';
this.data = null;
this.showContour = false;
}
var proto = Mesh3DTrace.prototype;
proto.handlePick = function(selection) {
if(selection.object === this.mesh) {
var selectIndex = selection.index = selection.data.index;
if(selection.data._cellCenter) {
selection.traceCoordinate = selection.data.dataCoordinate;
} else {
selection.traceCoordinate = [
this.data.x[selectIndex],
this.data.y[selectIndex],
this.data.z[selectIndex]
];
}
var text = this.data.hovertext || this.data.text;
if(Array.isArray(text) && text[selectIndex] !== undefined) {
selection.textLabel = text[selectIndex];
} else if(text) {
selection.textLabel = text;
}
return true;
}
};
function parseColorArray(colors) {
var b = [];
var len = colors.length;
for(var i = 0; i < len; i++) {
b[i] = str2RgbaArray(colors[i]);
}
return b;
}
// Unpack position data
function toDataCoords(axis, coord, scale, calendar) {
var b = [];
var len = coord.length;
for(var i = 0; i < len; i++) {
b[i] = axis.d2l(coord[i], 0, calendar) * scale;
}
return b;
}
// Round indices if passed as floats
function toRoundIndex(a) {
var b = [];
var len = a.length;
for(var i = 0; i < len; i++) {
b[i] = Math.round(a[i]);
}
return b;
}
function delaunayCells(delaunayaxis, positions) {
var d = ['x', 'y', 'z'].indexOf(delaunayaxis);
var b = [];
var len = positions.length;
for(var i = 0; i < len; i++) {
b[i] = [positions[i][(d + 1) % 3], positions[i][(d + 2) % 3]];
}
return triangulate(b);
}
// Validate indices
function hasValidIndices(list, numVertices) {
var len = list.length;
for(var i = 0; i < len; i++) {
if(list[i] <= -0.5 || list[i] >= numVertices - 0.5) { // Note: the indices would be rounded -0.49 is valid.
return false;
}
}
return true;
}
proto.update = function(data) {
var scene = this.scene;
var layout = scene.fullSceneLayout;
this.data = data;
var numVertices = data.x.length;
var positions = zip3(
toDataCoords(layout.xaxis, data.x, scene.dataScale[0], data.xcalendar),
toDataCoords(layout.yaxis, data.y, scene.dataScale[1], data.ycalendar),
toDataCoords(layout.zaxis, data.z, scene.dataScale[2], data.zcalendar)
);
var cells;
if(data.i && data.j && data.k) {
if(
data.i.length !== data.j.length ||
data.j.length !== data.k.length ||
!hasValidIndices(data.i, numVertices) ||
!hasValidIndices(data.j, numVertices) ||
!hasValidIndices(data.k, numVertices)
) {
return;
}
cells = zip3(
toRoundIndex(data.i),
toRoundIndex(data.j),
toRoundIndex(data.k)
);
} else if(data.alphahull === 0) {
cells = convexHull(positions);
} else if(data.alphahull > 0) {
cells = alphaShape(data.alphahull, positions);
} else {
cells = delaunayCells(data.delaunayaxis, positions);
}
var config = {
positions: positions,
cells: cells,
lightPosition: [data.lightposition.x, data.lightposition.y, data.lightposition.z],
ambient: data.lighting.ambient,
diffuse: data.lighting.diffuse,
specular: data.lighting.specular,
roughness: data.lighting.roughness,
fresnel: data.lighting.fresnel,
vertexNormalsEpsilon: data.lighting.vertexnormalsepsilon,
faceNormalsEpsilon: data.lighting.facenormalsepsilon,
opacity: data.opacity,
contourEnable: data.contour.show,
contourColor: str2RgbaArray(data.contour.color).slice(0, 3),
contourWidth: data.contour.width,
useFacetNormals: data.flatshading
};
if(data.intensity) {
var cOpts = extractOpts(data);
this.color = '#fff';
var mode = data.intensitymode;
config[mode + 'Intensity'] = data.intensity;
config[mode + 'IntensityBounds'] = [cOpts.min, cOpts.max];
config.colormap = parseColorScale(data);
} else if(data.vertexcolor) {
this.color = data.vertexcolor[0];
config.vertexColors = parseColorArray(data.vertexcolor);
} else if(data.facecolor) {
this.color = data.facecolor[0];
config.cellColors = parseColorArray(data.facecolor);
} else {
this.color = data.color;
config.meshColor = str2RgbaArray(data.color);
}
// Update mesh
this.mesh.update(config);
};
proto.dispose = function() {
this.scene.glplot.remove(this.mesh);
this.mesh.dispose();
};
function createMesh3DTrace(scene, data) {
var gl = scene.glplot.gl;
var mesh = createMesh({gl: gl});
var result = new Mesh3DTrace(scene, mesh, data.uid);
mesh._trace = result;
result.update(data);
scene.glplot.add(mesh);
return result;
}
module.exports = createMesh3DTrace;
},{"../../../stackgl_modules":1119,"../../components/colorscale":378,"../../lib/gl_format_color":499,"../../lib/str2rgbarray":528,"../../plots/gl3d/zip3":609}],869:[function(_dereq_,module,exports){
'use strict';
var Registry = _dereq_('../../registry');
var Lib = _dereq_('../../lib');
var colorscaleDefaults = _dereq_('../../components/colorscale/defaults');
var attributes = _dereq_('./attributes');
module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout) {
function coerce(attr, dflt) {
return Lib.coerce(traceIn, traceOut, attributes, attr, dflt);
}
// read in face/vertex properties
function readComponents(array) {
var ret = array.map(function(attr) {
var result = coerce(attr);
if(result && Lib.isArrayOrTypedArray(result)) return result;
return null;
});
return ret.every(function(x) {
return x && x.length === ret[0].length;
}) && ret;
}
var coords = readComponents(['x', 'y', 'z']);
if(!coords) {
traceOut.visible = false;
return;
}
readComponents(['i', 'j', 'k']);
// three indices should be all provided or not
if(
(traceOut.i && (!traceOut.j || !traceOut.k)) ||
(traceOut.j && (!traceOut.k || !traceOut.i)) ||
(traceOut.k && (!traceOut.i || !traceOut.j))
) {
traceOut.visible = false;
return;
}
var handleCalendarDefaults = Registry.getComponentMethod('calendars', 'handleTraceDefaults');
handleCalendarDefaults(traceIn, traceOut, ['x', 'y', 'z'], layout);
// Coerce remaining properties
[
'lighting.ambient',
'lighting.diffuse',
'lighting.specular',
'lighting.roughness',
'lighting.fresnel',
'lighting.vertexnormalsepsilon',
'lighting.facenormalsepsilon',
'lightposition.x',
'lightposition.y',
'lightposition.z',
'flatshading',
'alphahull',
'delaunayaxis',
'opacity'
].forEach(function(x) { coerce(x); });
var showContour = coerce('contour.show');
if(showContour) {
coerce('contour.color');
coerce('contour.width');
}
if('intensity' in traceIn) {
coerce('intensity');
coerce('intensitymode');
colorscaleDefaults(traceIn, traceOut, layout, coerce, {prefix: '', cLetter: 'c'});
} else {
traceOut.showscale = false;
if('facecolor' in traceIn) coerce('facecolor');
else if('vertexcolor' in traceIn) coerce('vertexcolor');
else coerce('color', defaultColor);
}
coerce('text');
coerce('hovertext');
coerce('hovertemplate');
coerce('xhoverformat');
coerce('yhoverformat');
coerce('zhoverformat');
// disable 1D transforms
// x/y/z should match lengths, and i/j/k should match as well, but
// the two sets have different lengths so transforms wouldn't work.
traceOut._length = null;
};
},{"../../components/colorscale/defaults":376,"../../lib":503,"../../registry":638,"./attributes":866}],870:[function(_dereq_,module,exports){
'use strict';
module.exports = {
attributes: _dereq_('./attributes'),
supplyDefaults: _dereq_('./defaults'),
calc: _dereq_('./calc'),
colorbar: {
min: 'cmin',
max: 'cmax'
},
plot: _dereq_('./convert'),
moduleType: 'trace',
name: 'mesh3d',
basePlotModule: _dereq_('../../plots/gl3d'),
categories: ['gl3d', 'showLegend'],
meta: {
}
};
},{"../../plots/gl3d":598,"./attributes":866,"./calc":867,"./convert":868,"./defaults":869}],871:[function(_dereq_,module,exports){
'use strict';
var extendFlat = _dereq_('../../lib').extendFlat;
var scatterAttrs = _dereq_('../scatter/attributes');
var axisHoverFormat = _dereq_('../../plots/cartesian/axis_format_attributes').axisHoverFormat;
var dash = _dereq_('../../components/drawing/attributes').dash;
var fxAttrs = _dereq_('../../components/fx/attributes');
var delta = _dereq_('../../constants/delta.js');
var INCREASING_COLOR = delta.INCREASING.COLOR;
var DECREASING_COLOR = delta.DECREASING.COLOR;
var lineAttrs = scatterAttrs.line;
function directionAttrs(lineColorDefault) {
return {
line: {
color: extendFlat({}, lineAttrs.color, {dflt: lineColorDefault}),
width: lineAttrs.width,
dash: dash,
editType: 'style'
},
editType: 'style'
};
}
module.exports = {
xperiod: scatterAttrs.xperiod,
xperiod0: scatterAttrs.xperiod0,
xperiodalignment: scatterAttrs.xperiodalignment,
xhoverformat: axisHoverFormat('x'),
yhoverformat: axisHoverFormat('y'),
x: {
valType: 'data_array',
editType: 'calc+clearAxisTypes',
},
open: {
valType: 'data_array',
editType: 'calc',
},
high: {
valType: 'data_array',
editType: 'calc',
},
low: {
valType: 'data_array',
editType: 'calc',
},
close: {
valType: 'data_array',
editType: 'calc',
},
line: {
width: extendFlat({}, lineAttrs.width, {
}),
dash: extendFlat({}, dash, {
}),
editType: 'style'
},
increasing: directionAttrs(INCREASING_COLOR),
decreasing: directionAttrs(DECREASING_COLOR),
text: {
valType: 'string',
dflt: '',
arrayOk: true,
editType: 'calc',
},
hovertext: {
valType: 'string',
dflt: '',
arrayOk: true,
editType: 'calc',
},
tickwidth: {
valType: 'number',
min: 0,
max: 0.5,
dflt: 0.3,
editType: 'calc',
},
hoverlabel: extendFlat({}, fxAttrs.hoverlabel, {
split: {
valType: 'boolean',
dflt: false,
editType: 'style',
}
}),
};
},{"../../components/drawing/attributes":387,"../../components/fx/attributes":397,"../../constants/delta.js":473,"../../lib":503,"../../plots/cartesian/axis_format_attributes":557,"../scatter/attributes":925}],872:[function(_dereq_,module,exports){
'use strict';
var Lib = _dereq_('../../lib');
var _ = Lib._;
var Axes = _dereq_('../../plots/cartesian/axes');
var alignPeriod = _dereq_('../../plots/cartesian/align_period');
var BADNUM = _dereq_('../../constants/numerical').BADNUM;
function calc(gd, trace) {
var xa = Axes.getFromId(gd, trace.xaxis);
var ya = Axes.getFromId(gd, trace.yaxis);
var tickLen = convertTickWidth(gd, xa, trace);
var minDiff = trace._minDiff;
trace._minDiff = null;
var origX = trace._origX;
trace._origX = null;
var x = trace._xcalc;
trace._xcalc = null;
var cd = calcCommon(gd, trace, origX, x, ya, ptFunc);
trace._extremes[xa._id] = Axes.findExtremes(xa, x, {vpad: minDiff / 2});
if(cd.length) {
Lib.extendFlat(cd[0].t, {
wHover: minDiff / 2,
tickLen: tickLen
});
return cd;
} else {
return [{t: {empty: true}}];
}
}
function ptFunc(o, h, l, c) {
return {
o: o,
h: h,
l: l,
c: c
};
}
// shared between OHLC and candlestick
// ptFunc makes a calcdata point specific to each trace type, from oi, hi, li, ci
function calcCommon(gd, trace, origX, x, ya, ptFunc) {
var o = ya.makeCalcdata(trace, 'open');
var h = ya.makeCalcdata(trace, 'high');
var l = ya.makeCalcdata(trace, 'low');
var c = ya.makeCalcdata(trace, 'close');
var hasTextArray = Array.isArray(trace.text);
var hasHovertextArray = Array.isArray(trace.hovertext);
// we're optimists - before we have any changing data, assume increasing
var increasing = true;
var cPrev = null;
var hasPeriod = !!trace.xperiodalignment;
var cd = [];
for(var i = 0; i < x.length; i++) {
var xi = x[i];
var oi = o[i];
var hi = h[i];
var li = l[i];
var ci = c[i];
if(xi !== BADNUM && oi !== BADNUM && hi !== BADNUM && li !== BADNUM && ci !== BADNUM) {
if(ci === oi) {
// if open == close, look for a change from the previous close
if(cPrev !== null && ci !== cPrev) increasing = ci > cPrev;
// else (c === cPrev or cPrev is null) no change
} else increasing = ci > oi;
cPrev = ci;
var pt = ptFunc(oi, hi, li, ci);
pt.pos = xi;
pt.yc = (oi + ci) / 2;
pt.i = i;
pt.dir = increasing ? 'increasing' : 'decreasing';
// For categoryorder, store low and high
pt.x = pt.pos;
pt.y = [li, hi];
if(hasPeriod) pt.orig_p = origX[i]; // used by hover
if(hasTextArray) pt.tx = trace.text[i];
if(hasHovertextArray) pt.htx = trace.hovertext[i];
cd.push(pt);
} else {
cd.push({pos: xi, empty: true});
}
}
trace._extremes[ya._id] = Axes.findExtremes(ya, Lib.concat(l, h), {padded: true});
if(cd.length) {
cd[0].t = {
labels: {
open: _(gd, 'open:') + ' ',
high: _(gd, 'high:') + ' ',
low: _(gd, 'low:') + ' ',
close: _(gd, 'close:') + ' '
}
};
}
return cd;
}
/*
* find min x-coordinates difference of all traces
* attached to this x-axis and stash the result in _minDiff
* in all traces; when a trace uses this in its
* calc step it deletes _minDiff, so that next calc this is
* done again in case the data changed.
* also since we need it here, stash _xcalc (and _origX) on the trace
*/
function convertTickWidth(gd, xa, trace) {
var minDiff = trace._minDiff;
if(!minDiff) {
var fullData = gd._fullData;
var ohlcTracesOnThisXaxis = [];
minDiff = Infinity;
var i;
for(i = 0; i < fullData.length; i++) {
var tracei = fullData[i];
if(tracei.type === 'ohlc' &&
tracei.visible === true &&
tracei.xaxis === xa._id
) {
ohlcTracesOnThisXaxis.push(tracei);
var origX = xa.makeCalcdata(tracei, 'x');
tracei._origX = origX;
var xcalc = alignPeriod(trace, xa, 'x', origX).vals;
tracei._xcalc = xcalc;
var _minDiff = Lib.distinctVals(xcalc).minDiff;
if(_minDiff && isFinite(_minDiff)) {
minDiff = Math.min(minDiff, _minDiff);
}
}
}
// if minDiff is still Infinity here, set it to 1
if(minDiff === Infinity) minDiff = 1;
for(i = 0; i < ohlcTracesOnThisXaxis.length; i++) {
ohlcTracesOnThisXaxis[i]._minDiff = minDiff;
}
}
return minDiff * trace.tickwidth;
}
module.exports = {
calc: calc,
calcCommon: calcCommon
};
},{"../../constants/numerical":479,"../../lib":503,"../../plots/cartesian/align_period":551,"../../plots/cartesian/axes":554}],873:[function(_dereq_,module,exports){
'use strict';
var Lib = _dereq_('../../lib');
var handleOHLC = _dereq_('./ohlc_defaults');
var handlePeriodDefaults = _dereq_('../scatter/period_defaults');
var attributes = _dereq_('./attributes');
module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout) {
function coerce(attr, dflt) {
return Lib.coerce(traceIn, traceOut, attributes, attr, dflt);
}
var len = handleOHLC(traceIn, traceOut, coerce, layout);
if(!len) {
traceOut.visible = false;
return;
}
handlePeriodDefaults(traceIn, traceOut, layout, coerce, {x: true});
coerce('xhoverformat');
coerce('yhoverformat');
coerce('line.width');
coerce('line.dash');
handleDirection(traceIn, traceOut, coerce, 'increasing');
handleDirection(traceIn, traceOut, coerce, 'decreasing');
coerce('text');
coerce('hovertext');
coerce('tickwidth');
layout._requestRangeslider[traceOut.xaxis] = true;
};
function handleDirection(traceIn, traceOut, coerce, direction) {
coerce(direction + '.line.color');
coerce(direction + '.line.width', traceOut.line.width);
coerce(direction + '.line.dash', traceOut.line.dash);
}
},{"../../lib":503,"../scatter/period_defaults":945,"./attributes":871,"./ohlc_defaults":876}],874:[function(_dereq_,module,exports){
'use strict';
var Axes = _dereq_('../../plots/cartesian/axes');
var Lib = _dereq_('../../lib');
var Fx = _dereq_('../../components/fx');
var Color = _dereq_('../../components/color');
var fillText = _dereq_('../../lib').fillText;
var delta = _dereq_('../../constants/delta.js');
var DIRSYMBOL = {
increasing: delta.INCREASING.SYMBOL,
decreasing: delta.DECREASING.SYMBOL
};
function hoverPoints(pointData, xval, yval, hovermode) {
var cd = pointData.cd;
var trace = cd[0].trace;
if(trace.hoverlabel.split) {
return hoverSplit(pointData, xval, yval, hovermode);
}
return hoverOnPoints(pointData, xval, yval, hovermode);
}
function _getClosestPoint(pointData, xval, yval, hovermode) {
var cd = pointData.cd;
var xa = pointData.xa;
var trace = cd[0].trace;
var t = cd[0].t;
var type = trace.type;
var minAttr = type === 'ohlc' ? 'l' : 'min';
var maxAttr = type === 'ohlc' ? 'h' : 'max';
var hoverPseudoDistance, spikePseudoDistance;
// potentially shift xval for grouped candlesticks
var centerShift = t.bPos || 0;
var shiftPos = function(di) { return di.pos + centerShift - xval; };
// ohlc and candlestick call displayHalfWidth different things...
var displayHalfWidth = t.bdPos || t.tickLen;
var hoverHalfWidth = t.wHover;
// if two figures are overlaying, let the narrowest one win
var pseudoDistance = Math.min(1, displayHalfWidth / Math.abs(xa.r2c(xa.range[1]) - xa.r2c(xa.range[0])));
hoverPseudoDistance = pointData.maxHoverDistance - pseudoDistance;
spikePseudoDistance = pointData.maxSpikeDistance - pseudoDistance;
function dx(di) {
var pos = shiftPos(di);
return Fx.inbox(pos - hoverHalfWidth, pos + hoverHalfWidth, hoverPseudoDistance);
}
function dy(di) {
var min = di[minAttr];
var max = di[maxAttr];
return min === max || Fx.inbox(min - yval, max - yval, hoverPseudoDistance);
}
function dxy(di) { return (dx(di) + dy(di)) / 2; }
var distfn = Fx.getDistanceFunction(hovermode, dx, dy, dxy);
Fx.getClosest(cd, distfn, pointData);
if(pointData.index === false) return null;
var di = cd[pointData.index];
if(di.empty) return null;
var dir = di.dir;
var container = trace[dir];
var lc = container.line.color;
if(Color.opacity(lc) && container.line.width) pointData.color = lc;
else pointData.color = container.fillcolor;
pointData.x0 = xa.c2p(di.pos + centerShift - displayHalfWidth, true);
pointData.x1 = xa.c2p(di.pos + centerShift + displayHalfWidth, true);
pointData.xLabelVal = di.orig_p !== undefined ? di.orig_p : di.pos;
pointData.spikeDistance = dxy(di) * spikePseudoDistance / hoverPseudoDistance;
pointData.xSpike = xa.c2p(di.pos, true);
return pointData;
}
function hoverSplit(pointData, xval, yval, hovermode) {
var cd = pointData.cd;
var ya = pointData.ya;
var trace = cd[0].trace;
var t = cd[0].t;
var closeBoxData = [];
var closestPoint = _getClosestPoint(pointData, xval, yval, hovermode);
// skip the rest (for this trace) if we didn't find a close point
if(!closestPoint) return [];
var cdIndex = closestPoint.index;
var di = cd[cdIndex];
var hoverinfo = di.hi || trace.hoverinfo;
var hoverParts = hoverinfo.split('+');
var isAll = hoverinfo === 'all';
var hasY = isAll || hoverParts.indexOf('y') !== -1;
// similar to hoverOnPoints, we return nothing
// if all or y is not present.
if(!hasY) return [];
var attrs = ['high', 'open', 'close', 'low'];
// several attributes can have the same y-coordinate. We will
// bunch them together in a single text block. For this, we keep
// a dictionary mapping y-coord -> point data.
var usedVals = {};
for(var i = 0; i < attrs.length; i++) {
var attr = attrs[i];
var val = trace[attr][closestPoint.index];
var valPx = ya.c2p(val, true);
var pointData2;
if(val in usedVals) {
pointData2 = usedVals[val];
pointData2.yLabel += '
' + t.labels[attr] + Axes.hoverLabelText(ya, val, trace.yhoverformat);
} else {
// copy out to a new object for each new y-value to label
pointData2 = Lib.extendFlat({}, closestPoint);
pointData2.y0 = pointData2.y1 = valPx;
pointData2.yLabelVal = val;
pointData2.yLabel = t.labels[attr] + Axes.hoverLabelText(ya, val, trace.yhoverformat);
pointData2.name = '';
closeBoxData.push(pointData2);
usedVals[val] = pointData2;
}
}
return closeBoxData;
}
function hoverOnPoints(pointData, xval, yval, hovermode) {
var cd = pointData.cd;
var ya = pointData.ya;
var trace = cd[0].trace;
var t = cd[0].t;
var closestPoint = _getClosestPoint(pointData, xval, yval, hovermode);
// skip the rest (for this trace) if we didn't find a close point
if(!closestPoint) return [];
// we don't make a calcdata point if we're missing any piece (x/o/h/l/c)
// so we need to fix the index here to point to the data arrays
var cdIndex = closestPoint.index;
var di = cd[cdIndex];
var i = closestPoint.index = di.i;
var dir = di.dir;
function getLabelLine(attr) {
return t.labels[attr] + Axes.hoverLabelText(ya, trace[attr][i], trace.yhoverformat);
}
var hoverinfo = di.hi || trace.hoverinfo;
var hoverParts = hoverinfo.split('+');
var isAll = hoverinfo === 'all';
var hasY = isAll || hoverParts.indexOf('y') !== -1;
var hasText = isAll || hoverParts.indexOf('text') !== -1;
var textParts = hasY ? [
getLabelLine('open'),
getLabelLine('high'),
getLabelLine('low'),
getLabelLine('close') + ' ' + DIRSYMBOL[dir]
] : [];
if(hasText) fillText(di, trace, textParts);
// don't make .yLabelVal or .text, since we're managing hoverinfo
// put it all in .extraText
closestPoint.extraText = textParts.join('
');
// this puts the label *and the spike* at the midpoint of the box, ie
// halfway between open and close, not between high and low.
closestPoint.y0 = closestPoint.y1 = ya.c2p(di.yc, true);
return [closestPoint];
}
module.exports = {
hoverPoints: hoverPoints,
hoverSplit: hoverSplit,
hoverOnPoints: hoverOnPoints
};
},{"../../components/color":366,"../../components/fx":406,"../../constants/delta.js":473,"../../lib":503,"../../plots/cartesian/axes":554}],875:[function(_dereq_,module,exports){
'use strict';
module.exports = {
moduleType: 'trace',
name: 'ohlc',
basePlotModule: _dereq_('../../plots/cartesian'),
categories: ['cartesian', 'svg', 'showLegend'],
meta: {
},
attributes: _dereq_('./attributes'),
supplyDefaults: _dereq_('./defaults'),
calc: _dereq_('./calc').calc,
plot: _dereq_('./plot'),
style: _dereq_('./style'),
hoverPoints: _dereq_('./hover').hoverPoints,
selectPoints: _dereq_('./select')
};
},{"../../plots/cartesian":568,"./attributes":871,"./calc":872,"./defaults":873,"./hover":874,"./plot":877,"./select":878,"./style":879}],876:[function(_dereq_,module,exports){
'use strict';
var Registry = _dereq_('../../registry');
var Lib = _dereq_('../../lib');
module.exports = function handleOHLC(traceIn, traceOut, coerce, layout) {
var x = coerce('x');
var open = coerce('open');
var high = coerce('high');
var low = coerce('low');
var close = coerce('close');
coerce('hoverlabel.split');
var handleCalendarDefaults = Registry.getComponentMethod('calendars', 'handleTraceDefaults');
handleCalendarDefaults(traceIn, traceOut, ['x'], layout);
if(!(open && high && low && close)) return;
var len = Math.min(open.length, high.length, low.length, close.length);
if(x) len = Math.min(len, Lib.minRowLength(x));
traceOut._length = len;
return len;
};
},{"../../lib":503,"../../registry":638}],877:[function(_dereq_,module,exports){
'use strict';
var d3 = _dereq_('@plotly/d3');
var Lib = _dereq_('../../lib');
module.exports = function plot(gd, plotinfo, cdOHLC, ohlcLayer) {
var ya = plotinfo.yaxis;
var xa = plotinfo.xaxis;
var posHasRangeBreaks = !!xa.rangebreaks;
Lib.makeTraceGroups(ohlcLayer, cdOHLC, 'trace ohlc').each(function(cd) {
var plotGroup = d3.select(this);
var cd0 = cd[0];
var t = cd0.t;
var trace = cd0.trace;
if(trace.visible !== true || t.empty) {
plotGroup.remove();
return;
}
var tickLen = t.tickLen;
var paths = plotGroup.selectAll('path').data(Lib.identity);
paths.enter().append('path');
paths.exit().remove();
paths.attr('d', function(d) {
if(d.empty) return 'M0,0Z';
var xo = xa.c2p(d.pos - tickLen, true);
var xc = xa.c2p(d.pos + tickLen, true);
var x = posHasRangeBreaks ? (xo + xc) / 2 : xa.c2p(d.pos, true);
var yo = ya.c2p(d.o, true);
var yh = ya.c2p(d.h, true);
var yl = ya.c2p(d.l, true);
var yc = ya.c2p(d.c, true);
return 'M' + xo + ',' + yo + 'H' + x +
'M' + x + ',' + yh + 'V' + yl +
'M' + xc + ',' + yc + 'H' + x;
});
});
};
},{"../../lib":503,"@plotly/d3":58}],878:[function(_dereq_,module,exports){
'use strict';
module.exports = function selectPoints(searchInfo, selectionTester) {
var cd = searchInfo.cd;
var xa = searchInfo.xaxis;
var ya = searchInfo.yaxis;
var selection = [];
var i;
// for (potentially grouped) candlesticks
var posOffset = cd[0].t.bPos || 0;
if(selectionTester === false) {
// clear selection
for(i = 0; i < cd.length; i++) {
cd[i].selected = 0;
}
} else {
for(i = 0; i < cd.length; i++) {
var di = cd[i];
if(selectionTester.contains([xa.c2p(di.pos + posOffset), ya.c2p(di.yc)], null, di.i, searchInfo)) {
selection.push({
pointNumber: di.i,
x: xa.c2d(di.pos),
y: ya.c2d(di.yc)
});
di.selected = 1;
} else {
di.selected = 0;
}
}
}
return selection;
};
},{}],879:[function(_dereq_,module,exports){
'use strict';
var d3 = _dereq_('@plotly/d3');
var Drawing = _dereq_('../../components/drawing');
var Color = _dereq_('../../components/color');
module.exports = function style(gd, cd, sel) {
var s = sel ? sel : d3.select(gd).selectAll('g.ohlclayer').selectAll('g.trace');
s.style('opacity', function(d) {
return d[0].trace.opacity;
});
s.each(function(d) {
var trace = d[0].trace;
d3.select(this).selectAll('path').each(function(di) {
if(di.empty) return;
var dirLine = trace[di.dir].line;
d3.select(this)
.style('fill', 'none')
.call(Color.stroke, dirLine.color)
.call(Drawing.dashLine, dirLine.dash, dirLine.width)
// TODO: custom selection style for OHLC
.style('opacity', trace.selectedpoints && !di.selected ? 0.3 : 1);
});
});
};
},{"../../components/color":366,"../../components/drawing":388,"@plotly/d3":58}],880:[function(_dereq_,module,exports){
'use strict';
var extendFlat = _dereq_('../../lib/extend').extendFlat;
var baseAttrs = _dereq_('../../plots/attributes');
var fontAttrs = _dereq_('../../plots/font_attributes');
var colorScaleAttrs = _dereq_('../../components/colorscale/attributes');
var hovertemplateAttrs = _dereq_('../../plots/template_attributes').hovertemplateAttrs;
var domainAttrs = _dereq_('../../plots/domain').attributes;
var line = extendFlat(
{editType: 'calc'},
colorScaleAttrs('line', {editTypeOverride: 'calc'}),
{
shape: {
valType: 'enumerated',
values: ['linear', 'hspline'],
dflt: 'linear',
editType: 'plot',
},
hovertemplate: hovertemplateAttrs({
editType: 'plot',
arrayOk: false
}, {
keys: ['count', 'probability'],
})
}
);
module.exports = {
domain: domainAttrs({name: 'parcats', trace: true, editType: 'calc'}),
hoverinfo: extendFlat({}, baseAttrs.hoverinfo, {
flags: ['count', 'probability'],
editType: 'plot',
arrayOk: false
}),
hoveron: {
valType: 'enumerated',
values: ['category', 'color', 'dimension'],
dflt: 'category',
editType: 'plot',
},
hovertemplate: hovertemplateAttrs({
editType: 'plot',
arrayOk: false
}, {
keys: [
'count', 'probability', 'category',
'categorycount', 'colorcount', 'bandcolorcount'
],
}),
arrangement: {
valType: 'enumerated',
values: ['perpendicular', 'freeform', 'fixed'],
dflt: 'perpendicular',
editType: 'plot',
},
bundlecolors: {
valType: 'boolean',
dflt: true,
editType: 'plot',
},
sortpaths: {
valType: 'enumerated',
values: ['forward', 'backward'],
dflt: 'forward',
editType: 'plot',
},
labelfont: fontAttrs({
editType: 'calc',
}),
tickfont: fontAttrs({
editType: 'calc',
}),
dimensions: {
_isLinkedToArray: 'dimension',
label: {
valType: 'string',
editType: 'calc',
},
categoryorder: {
valType: 'enumerated',
values: [
'trace', 'category ascending', 'category descending', 'array'
],
dflt: 'trace',
editType: 'calc',
},
categoryarray: {
valType: 'data_array',
editType: 'calc',
},
ticktext: {
valType: 'data_array',
editType: 'calc',
},
values: {
valType: 'data_array',
dflt: [],
editType: 'calc',
},
displayindex: {
valType: 'integer',
editType: 'calc',
},
editType: 'calc',
visible: {
valType: 'boolean',
dflt: true,
editType: 'calc',
}
},
line: line,
counts: {
valType: 'number',
min: 0,
dflt: 1,
arrayOk: true,
editType: 'calc',
},
// Hide unsupported top-level properties from plot-schema
customdata: undefined,
hoverlabel: undefined,
ids: undefined,
legendgroup: undefined,
legendrank: undefined,
opacity: undefined,
selectedpoints: undefined,
showlegend: undefined
};
},{"../../components/colorscale/attributes":373,"../../lib/extend":493,"../../plots/attributes":550,"../../plots/domain":584,"../../plots/font_attributes":585,"../../plots/template_attributes":633}],881:[function(_dereq_,module,exports){
'use strict';
var getModuleCalcData = _dereq_('../../plots/get_data').getModuleCalcData;
var parcatsPlot = _dereq_('./plot');
var PARCATS = 'parcats';
exports.name = PARCATS;
exports.plot = function(gd, traces, transitionOpts, makeOnCompleteCallback) {
var cdModuleAndOthers = getModuleCalcData(gd.calcdata, PARCATS);
if(cdModuleAndOthers.length) {
var calcData = cdModuleAndOthers[0];
parcatsPlot(gd, calcData, transitionOpts, makeOnCompleteCallback);
}
};
exports.clean = function(newFullData, newFullLayout, oldFullData, oldFullLayout) {
var hadTable = (oldFullLayout._has && oldFullLayout._has('parcats'));
var hasTable = (newFullLayout._has && newFullLayout._has('parcats'));
if(hadTable && !hasTable) {
oldFullLayout._paperdiv.selectAll('.parcats').remove();
}
};
},{"../../plots/get_data":593,"./plot":886}],882:[function(_dereq_,module,exports){
'use strict';
// Requirements
// ============
var wrap = _dereq_('../../lib/gup').wrap;
var hasColorscale = _dereq_('../../components/colorscale/helpers').hasColorscale;
var colorscaleCalc = _dereq_('../../components/colorscale/calc');
var filterUnique = _dereq_('../../lib/filter_unique.js');
var Drawing = _dereq_('../../components/drawing');
var Lib = _dereq_('../../lib');
var isNumeric = _dereq_('fast-isnumeric');
/**
* Create a wrapped ParcatsModel object from trace
*
* Note: trace defaults have already been applied
* @param {Object} gd
* @param {Object} trace
* @return {Array.
}
*/
module.exports = function calc(gd, trace) {
var visibleDims = Lib.filterVisible(trace.dimensions);
if(visibleDims.length === 0) return [];
var uniqueInfoDims = visibleDims.map(function(dim) {
var categoryValues;
if(dim.categoryorder === 'trace') {
// Use order of first occurrence in trace
categoryValues = null;
} else if(dim.categoryorder === 'array') {
// Use categories specified in `categoryarray` first,
// then add extra to the end in trace order
categoryValues = dim.categoryarray;
} else {
// Get all categories up front
categoryValues = filterUnique(dim.values);
// order them
var allNumeric = true;
for(var i = 0; i < categoryValues.length; i++) {
if(!isNumeric(categoryValues[i])) {
allNumeric = false;
break;
}
}
categoryValues.sort(allNumeric ? Lib.sorterAsc : undefined);
if(dim.categoryorder === 'category descending') {
categoryValues = categoryValues.reverse();
}
}
return getUniqueInfo(dim.values, categoryValues);
});
var counts,
count,
totalCount;
if(Lib.isArrayOrTypedArray(trace.counts)) {
counts = trace.counts;
} else {
counts = [trace.counts];
}
validateDimensionDisplayInds(visibleDims);
visibleDims.forEach(function(dim, dimInd) {
validateCategoryProperties(dim, uniqueInfoDims[dimInd]);
});
// Handle path colors
// ------------------
var line = trace.line;
var markerColorscale;
// Process colorscale
if(line) {
if(hasColorscale(trace, 'line')) {
colorscaleCalc(gd, trace, {
vals: trace.line.color,
containerStr: 'line',
cLetter: 'c'
});
}
markerColorscale = Drawing.tryColorscale(line);
} else {
markerColorscale = Lib.identity;
}
// Build color generation function
function getMarkerColorInfo(index) {
var value, rawColor;
if(Lib.isArrayOrTypedArray(line.color)) {
value = line.color[index % line.color.length];
rawColor = value;
} else {
value = line.color;
}
return {color: markerColorscale(value), rawColor: rawColor};
}
// Number of values and counts
// ---------------------------
var numValues = visibleDims[0].values.length;
// Build path info
// ---------------
// Mapping from category inds to PathModel objects
var pathModels = {};
// Category inds array for each dimension
var categoryIndsDims = uniqueInfoDims.map(function(di) {return di.inds;});
// Initialize total count
totalCount = 0;
var valueInd;
var d;
for(valueInd = 0; valueInd < numValues; valueInd++) {
// Category inds for this input value across dimensions
var categoryIndsPath = [];
for(d = 0; d < categoryIndsDims.length; d++) {
categoryIndsPath.push(categoryIndsDims[d][valueInd]);
}
// Count
count = counts[valueInd % counts.length];
// Update total count
totalCount += count;
// Path color
var pathColorInfo = getMarkerColorInfo(valueInd);
// path key
var pathKey = categoryIndsPath + '-' + pathColorInfo.rawColor;
// Create / Update PathModel
if(pathModels[pathKey] === undefined) {
pathModels[pathKey] = createPathModel(categoryIndsPath,
pathColorInfo.color,
pathColorInfo.rawColor);
}
updatePathModel(pathModels[pathKey], valueInd, count);
}
var dimensionModels = visibleDims.map(function(di, i) {
return createDimensionModel(i, di._index, di._displayindex, di.label, totalCount);
});
for(valueInd = 0; valueInd < numValues; valueInd++) {
count = counts[valueInd % counts.length];
for(d = 0; d < dimensionModels.length; d++) {
var containerInd = dimensionModels[d].containerInd;
var catInd = uniqueInfoDims[d].inds[valueInd];
var cats = dimensionModels[d].categories;
if(cats[catInd] === undefined) {
var catValue = trace.dimensions[containerInd]._categoryarray[catInd];
var catLabel = trace.dimensions[containerInd]._ticktext[catInd];
cats[catInd] = createCategoryModel(d, catInd, catValue, catLabel);
}
updateCategoryModel(cats[catInd], valueInd, count);
}
}
// Compute unique
return wrap(createParcatsModel(dimensionModels, pathModels, totalCount));
};
// Models
// ======
// Parcats Model
// -------------
/**
* @typedef {Object} ParcatsModel
* Object containing calculated information about a parcats trace
*
* @property {Array.} dimensions
* Array of dimension models
* @property {Object.} paths
* Dictionary from category inds string (e.g. "1,2,1,1") to path model
* @property {Number} maxCats
* The maximum number of categories of any dimension in the diagram
* @property {Number} count
* Total number of input values
* @property {Object} trace
*/
/**
* Create and new ParcatsModel object
* @param {Array.} dimensions
* @param {Object.} paths
* @param {Number} count
* @return {ParcatsModel}
*/
function createParcatsModel(dimensions, paths, count) {
var maxCats = dimensions
.map(function(d) {return d.categories.length;})
.reduce(function(v1, v2) {return Math.max(v1, v2);});
return {dimensions: dimensions, paths: paths, trace: undefined, maxCats: maxCats, count: count};
}
// Dimension Model
// ---------------
/**
* @typedef {Object} DimensionModel
* Object containing calculated information about a single dimension
*
* @property {Number} dimensionInd
* The index of this dimension among the *visible* dimensions
* @property {Number} containerInd
* The index of this dimension in the original dimensions container,
* irrespective of dimension visibility
* @property {Number} displayInd
* The display index of this dimension (where 0 is the left most dimension)
* @property {String} dimensionLabel
* The label of this dimension
* @property {Number} count
* Total number of input values
* @property {Array.} categories
* @property {Number|null} dragX
* The x position of dimension that is currently being dragged. null if not being dragged
*/
/**
* Create and new DimensionModel object with an empty categories array
* @param {Number} dimensionInd
* @param {Number} containerInd
* @param {Number} displayInd
* @param {String} dimensionLabel
* @param {Number} count
* Total number of input values
* @return {DimensionModel}
*/
function createDimensionModel(dimensionInd, containerInd, displayInd, dimensionLabel, count) {
return {
dimensionInd: dimensionInd,
containerInd: containerInd,
displayInd: displayInd,
dimensionLabel: dimensionLabel,
count: count,
categories: [],
dragX: null
};
}
// Category Model
// --------------
/**
* @typedef {Object} CategoryModel
* Object containing calculated information about a single category.
*
* @property {Number} dimensionInd
* The index of this categories dimension
* @property {Number} categoryInd
* The index of this category
* @property {Number} displayInd
* The display index of this category (where 0 is the topmost category)
* @property {String} categoryLabel
* The name of this category
* @property categoryValue: Raw value of the category
* @property {Array} valueInds
* Array of indices (into the original value array) of all samples in this category
* @property {Number} count
* The number of elements from the original array in this path
* @property {Number|null} dragY
* The y position of category that is currently being dragged. null if not being dragged
*/
/**
* Create and return a new CategoryModel object
* @param {Number} dimensionInd
* @param {Number} categoryInd
* The display index of this category (where 0 is the topmost category)
* @param {String} categoryValue
* @param {String} categoryLabel
* @return {CategoryModel}
*/
function createCategoryModel(dimensionInd, categoryInd, categoryValue, categoryLabel) {
return {
dimensionInd: dimensionInd,
categoryInd: categoryInd,
categoryValue: categoryValue,
displayInd: categoryInd,
categoryLabel: categoryLabel,
valueInds: [],
count: 0,
dragY: null
};
}
/**
* Update a CategoryModel object with a new value index
* Note: The calling parameter is modified in place.
*
* @param {CategoryModel} categoryModel
* @param {Number} valueInd
* @param {Number} count
*/
function updateCategoryModel(categoryModel, valueInd, count) {
categoryModel.valueInds.push(valueInd);
categoryModel.count += count;
}
// Path Model
// ----------
/**
* @typedef {Object} PathModel
* Object containing calculated information about the samples in a path.
*
* @property {Array} categoryInds
* Array of category indices for each dimension (length `numDimensions`)
* @param {String} pathColor
* Color of this path. (Note: Any colorscaling has already taken place)
* @property {Array} valueInds
* Array of indices (into the original value array) of all samples in this path
* @property {Number} count
* The number of elements from the original array in this path
* @property {String} color
* The path's color (ass CSS color string)
* @property rawColor
* The raw color value specified by the user. May be a CSS color string or a Number
*/
/**
* Create and return a new PathModel object
* @param {Array} categoryInds
* @param color
* @param rawColor
* @return {PathModel}
*/
function createPathModel(categoryInds, color, rawColor) {
return {
categoryInds: categoryInds,
color: color,
rawColor: rawColor,
valueInds: [],
count: 0
};
}
/**
* Update a PathModel object with a new value index
* Note: The calling parameter is modified in place.
*
* @param {PathModel} pathModel
* @param {Number} valueInd
* @param {Number} count
*/
function updatePathModel(pathModel, valueInd, count) {
pathModel.valueInds.push(valueInd);
pathModel.count += count;
}
// Unique calculations
// ===================
/**
* @typedef {Object} UniqueInfo
* Object containing information about the unique values of an input array
*
* @property {Array} uniqueValues
* The unique values in the input array
* @property {Array} uniqueCounts
* The number of times each entry in uniqueValues occurs in input array.
* This has the same length as `uniqueValues`
* @property {Array} inds
* Indices into uniqueValues that would reproduce original input array
*/
/**
* Compute unique value information for an array
*
* IMPORTANT: Note that values are considered unique
* if their string representations are unique.
*
* @param {Array} values
* @param {Array|undefined} uniqueValues
* Array of expected unique values. The uniqueValues property of the resulting UniqueInfo object will begin with
* these entries. Entries are included even if there are zero occurrences in the values array. Entries found in
* the values array that are not present in uniqueValues will be included at the end of the array in the
* UniqueInfo object.
* @return {UniqueInfo}
*/
function getUniqueInfo(values, uniqueValues) {
// Initialize uniqueValues if not specified
if(uniqueValues === undefined || uniqueValues === null) {
uniqueValues = [];
} else {
// Shallow copy so append below doesn't alter input array
uniqueValues = uniqueValues.map(function(e) {return e;});
}
// Initialize Variables
var uniqueValueCounts = {};
var uniqueValueInds = {};
var inds = [];
// Initialize uniqueValueCounts and
uniqueValues.forEach(function(uniqueVal, valInd) {
uniqueValueCounts[uniqueVal] = 0;
uniqueValueInds[uniqueVal] = valInd;
});
// Compute the necessary unique info in a single pass
for(var i = 0; i < values.length; i++) {
var item = values[i];
var itemInd;
if(uniqueValueCounts[item] === undefined) {
// This item has a previously unseen value
uniqueValueCounts[item] = 1;
itemInd = uniqueValues.push(item) - 1;
uniqueValueInds[item] = itemInd;
} else {
// Increment count for this item
uniqueValueCounts[item]++;
itemInd = uniqueValueInds[item];
}
inds.push(itemInd);
}
// Build UniqueInfo
var uniqueCounts = uniqueValues.map(function(v) { return uniqueValueCounts[v]; });
return {
uniqueValues: uniqueValues,
uniqueCounts: uniqueCounts,
inds: inds
};
}
/**
* Validate the requested display order for the dimensions.
* If the display order is a permutation of 0 through dimensions.length - 1, link to _displayindex
* Otherwise, replace the display order with the dimension order
* @param {Object} trace
*/
function validateDimensionDisplayInds(visibleDims) {
var displayInds = visibleDims.map(function(d) { return d.displayindex; });
var i;
if(isRangePermutation(displayInds)) {
for(i = 0; i < visibleDims.length; i++) {
visibleDims[i]._displayindex = visibleDims[i].displayindex;
}
} else {
for(i = 0; i < visibleDims.length; i++) {
visibleDims[i]._displayindex = i;
}
}
}
/**
* Update category properties based on the unique values found for this dimension
* @param {Object} dim
* @param {UniqueInfo} uniqueInfoDim
*/
function validateCategoryProperties(dim, uniqueInfoDim) {
// Update categoryarray
dim._categoryarray = uniqueInfoDim.uniqueValues;
// Handle ticktext
if(dim.ticktext === null || dim.ticktext === undefined) {
dim._ticktext = [];
} else {
// Shallow copy to avoid modifying input array
dim._ticktext = dim.ticktext.slice();
}
// Extend ticktext with elements from uniqueInfoDim.uniqueValues
for(var i = dim._ticktext.length; i < uniqueInfoDim.uniqueValues.length; i++) {
dim._ticktext.push(uniqueInfoDim.uniqueValues[i]);
}
}
/**
* Determine whether an array contains a permutation of the integers from 0 to the array's length - 1
* @param {Array} inds
* @return {boolean}
*/
function isRangePermutation(inds) {
var indsSpecified = new Array(inds.length);
for(var i = 0; i < inds.length; i++) {
// Check for out of bounds
if(inds[i] < 0 || inds[i] >= inds.length) {
return false;
}
// Check for collisions with already specified index
if(indsSpecified[inds[i]] !== undefined) {
return false;
}
indsSpecified[inds[i]] = true;
}
// Nothing out of bounds and no collisions. We have a permutation
return true;
}
},{"../../components/colorscale/calc":374,"../../components/colorscale/helpers":377,"../../components/drawing":388,"../../lib":503,"../../lib/filter_unique.js":494,"../../lib/gup":500,"fast-isnumeric":190}],883:[function(_dereq_,module,exports){
'use strict';
var Lib = _dereq_('../../lib');
var hasColorscale = _dereq_('../../components/colorscale/helpers').hasColorscale;
var colorscaleDefaults = _dereq_('../../components/colorscale/defaults');
var handleDomainDefaults = _dereq_('../../plots/domain').defaults;
var handleArrayContainerDefaults = _dereq_('../../plots/array_container_defaults');
var attributes = _dereq_('./attributes');
var mergeLength = _dereq_('../parcoords/merge_length');
function handleLineDefaults(traceIn, traceOut, defaultColor, layout, coerce) {
coerce('line.shape');
coerce('line.hovertemplate');
var lineColor = coerce('line.color', layout.colorway[0]);
if(hasColorscale(traceIn, 'line') && Lib.isArrayOrTypedArray(lineColor)) {
if(lineColor.length) {
coerce('line.colorscale');
colorscaleDefaults(traceIn, traceOut, layout, coerce, {prefix: 'line.', cLetter: 'c'});
return lineColor.length;
} else {
traceOut.line.color = defaultColor;
}
}
return Infinity;
}
function dimensionDefaults(dimensionIn, dimensionOut) {
function coerce(attr, dflt) {
return Lib.coerce(dimensionIn, dimensionOut, attributes.dimensions, attr, dflt);
}
var values = coerce('values');
var visible = coerce('visible');
if(!(values && values.length)) {
visible = dimensionOut.visible = false;
}
if(visible) {
// Dimension level
coerce('label');
coerce('displayindex', dimensionOut._index);
// Category level
var arrayIn = dimensionIn.categoryarray;
var isValidArray = (Array.isArray(arrayIn) && arrayIn.length > 0);
var orderDefault;
if(isValidArray) orderDefault = 'array';
var order = coerce('categoryorder', orderDefault);
// coerce 'categoryarray' only in array order case
if(order === 'array') {
coerce('categoryarray');
coerce('ticktext');
} else {
delete dimensionIn.categoryarray;
delete dimensionIn.ticktext;
}
// cannot set 'categoryorder' to 'array' with an invalid 'categoryarray'
if(!isValidArray && order === 'array') {
dimensionOut.categoryorder = 'trace';
}
}
}
module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout) {
function coerce(attr, dflt) {
return Lib.coerce(traceIn, traceOut, attributes, attr, dflt);
}
var dimensions = handleArrayContainerDefaults(traceIn, traceOut, {
name: 'dimensions',
handleItemDefaults: dimensionDefaults
});
var len = handleLineDefaults(traceIn, traceOut, defaultColor, layout, coerce);
handleDomainDefaults(traceOut, layout, coerce);
if(!Array.isArray(dimensions) || !dimensions.length) {
traceOut.visible = false;
}
mergeLength(traceOut, dimensions, 'values', len);
coerce('hoveron');
coerce('hovertemplate');
coerce('arrangement');
coerce('bundlecolors');
coerce('sortpaths');
coerce('counts');
var labelfontDflt = {
family: layout.font.family,
size: Math.round(layout.font.size),
color: layout.font.color
};
Lib.coerceFont(coerce, 'labelfont', labelfontDflt);
var categoryfontDefault = {
family: layout.font.family,
size: Math.round(layout.font.size / 1.2),
color: layout.font.color
};
Lib.coerceFont(coerce, 'tickfont', categoryfontDefault);
};
},{"../../components/colorscale/defaults":376,"../../components/colorscale/helpers":377,"../../lib":503,"../../plots/array_container_defaults":549,"../../plots/domain":584,"../parcoords/merge_length":896,"./attributes":880}],884:[function(_dereq_,module,exports){
'use strict';
module.exports = {
attributes: _dereq_('./attributes'),
supplyDefaults: _dereq_('./defaults'),
calc: _dereq_('./calc'),
plot: _dereq_('./plot'),
colorbar: {
container: 'line',
min: 'cmin',
max: 'cmax'
},
moduleType: 'trace',
name: 'parcats',
basePlotModule: _dereq_('./base_plot'),
categories: ['noOpacity'],
meta: {
}
};
},{"./attributes":880,"./base_plot":881,"./calc":882,"./defaults":883,"./plot":886}],885:[function(_dereq_,module,exports){
'use strict';
var d3 = _dereq_('@plotly/d3');
var interpolateNumber = _dereq_('d3-interpolate').interpolateNumber;
var Plotly = _dereq_('../../plot_api/plot_api');
var Fx = _dereq_('../../components/fx');
var Lib = _dereq_('../../lib');
var strTranslate = Lib.strTranslate;
var Drawing = _dereq_('../../components/drawing');
var tinycolor = _dereq_('tinycolor2');
var svgTextUtils = _dereq_('../../lib/svg_text_utils');
function performPlot(parcatsModels, graphDiv, layout, svg) {
var viewModels = parcatsModels.map(createParcatsViewModel.bind(0, graphDiv, layout));
// Get (potentially empty) parcatslayer selection with bound data to single element array
var layerSelection = svg.selectAll('g.parcatslayer').data([null]);
// Initialize single parcatslayer group if it doesn't exist
layerSelection.enter()
.append('g')
.attr('class', 'parcatslayer')
.style('pointer-events', 'all');
// Bind data to children of layerSelection and get reference to traceSelection
var traceSelection = layerSelection
.selectAll('g.trace.parcats')
.data(viewModels, key);
// Initialize group for each trace/dimensions
var traceEnter = traceSelection.enter()
.append('g')
.attr('class', 'trace parcats');
// Update properties for each trace
traceSelection
.attr('transform', function(d) {
return strTranslate(d.x, d.y);
});
// Initialize paths group
traceEnter
.append('g')
.attr('class', 'paths');
// Update paths transform
var pathsSelection = traceSelection
.select('g.paths');
// Get paths selection
var pathSelection = pathsSelection
.selectAll('path.path')
.data(function(d) {
return d.paths;
}, key);
// Update existing path colors
pathSelection
.attr('fill', function(d) {
return d.model.color;
});
// Create paths
var pathSelectionEnter = pathSelection
.enter()
.append('path')
.attr('class', 'path')
.attr('stroke-opacity', 0)
.attr('fill', function(d) {
return d.model.color;
})
.attr('fill-opacity', 0);
stylePathsNoHover(pathSelectionEnter);
// Set path geometry
pathSelection
.attr('d', function(d) {
return d.svgD;
});
// sort paths
if(!pathSelectionEnter.empty()) {
// Only sort paths if there has been a change.
// Otherwise paths are already sorted or a hover operation may be in progress
pathSelection.sort(compareRawColor);
}
// Remove any old paths
pathSelection.exit().remove();
// Path hover
pathSelection
.on('mouseover', mouseoverPath)
.on('mouseout', mouseoutPath)
.on('click', clickPath);
// Initialize dimensions group
traceEnter.append('g').attr('class', 'dimensions');
// Update dimensions transform
var dimensionsSelection = traceSelection
.select('g.dimensions');
// Get dimension selection
var dimensionSelection = dimensionsSelection
.selectAll('g.dimension')
.data(function(d) {
return d.dimensions;
}, key);
// Create dimension groups
dimensionSelection.enter()
.append('g')
.attr('class', 'dimension');
// Update dimension group transforms
dimensionSelection.attr('transform', function(d) {
return strTranslate(d.x, 0);
});
// Remove any old dimensions
dimensionSelection.exit().remove();
// Get category selection
var categorySelection = dimensionSelection
.selectAll('g.category')
.data(function(d) {
return d.categories;
}, key);
// Initialize category groups
var categoryGroupEnterSelection = categorySelection
.enter()
.append('g')
.attr('class', 'category');
// Update category transforms
categorySelection
.attr('transform', function(d) {
return strTranslate(0, d.y);
});
// Initialize rectangle
categoryGroupEnterSelection
.append('rect')
.attr('class', 'catrect')
.attr('pointer-events', 'none');
// Update rectangle
categorySelection.select('rect.catrect')
.attr('fill', 'none')
.attr('width', function(d) {
return d.width;
})
.attr('height', function(d) {
return d.height;
});
styleCategoriesNoHover(categoryGroupEnterSelection);
// Initialize color band rects
var bandSelection = categorySelection
.selectAll('rect.bandrect')
.data(
/** @param {CategoryViewModel} catViewModel*/
function(catViewModel) {
return catViewModel.bands;
}, key);
// Raise all update bands to the top so that fading enter/exit bands will be behind
bandSelection.each(function() {Lib.raiseToTop(this);});
// Update band color
bandSelection
.attr('fill', function(d) {
return d.color;
});
var bandsSelectionEnter = bandSelection.enter()
.append('rect')
.attr('class', 'bandrect')
.attr('stroke-opacity', 0)
.attr('fill', function(d) {
return d.color;
})
.attr('fill-opacity', 0);
bandSelection
.attr('fill', function(d) {
return d.color;
})
.attr('width', function(d) {
return d.width;
})
.attr('height', function(d) {
return d.height;
})
.attr('y', function(d) {
return d.y;
})
.attr('cursor',
/** @param {CategoryBandViewModel} bandModel*/
function(bandModel) {
if(bandModel.parcatsViewModel.arrangement === 'fixed') {
return 'default';
} else if(bandModel.parcatsViewModel.arrangement === 'perpendicular') {
return 'ns-resize';
} else {
return 'move';
}
});
styleBandsNoHover(bandsSelectionEnter);
bandSelection.exit().remove();
// Initialize category label
categoryGroupEnterSelection
.append('text')
.attr('class', 'catlabel')
.attr('pointer-events', 'none');
var paperColor = graphDiv._fullLayout.paper_bgcolor;
// Update category label
categorySelection.select('text.catlabel')
.attr('text-anchor',
function(d) {
if(catInRightDim(d)) {
// Place label to the right of category
return 'start';
} else {
// Place label to the left of category
return 'end';
}
})
.attr('alignment-baseline', 'middle')
.style('text-shadow', svgTextUtils.makeTextShadow(paperColor))
.style('fill', 'rgb(0, 0, 0)')
.attr('x',
function(d) {
if(catInRightDim(d)) {
// Place label to the right of category
return d.width + 5;
} else {
// Place label to the left of category
return -5;
}
})
.attr('y', function(d) {
return d.height / 2;
})
.text(function(d) {
return d.model.categoryLabel;
})
.each(
/** @param {CategoryViewModel} catModel*/
function(catModel) {
Drawing.font(d3.select(this), catModel.parcatsViewModel.categorylabelfont);
svgTextUtils.convertToTspans(d3.select(this), graphDiv);
});
// Initialize dimension label
categoryGroupEnterSelection
.append('text')
.attr('class', 'dimlabel');
// Update dimension label
categorySelection.select('text.dimlabel')
.attr('text-anchor', 'middle')
.attr('alignment-baseline', 'baseline')
.attr('cursor',
/** @param {CategoryViewModel} catModel*/
function(catModel) {
if(catModel.parcatsViewModel.arrangement === 'fixed') {
return 'default';
} else {
return 'ew-resize';
}
})
.attr('x', function(d) {
return d.width / 2;
})
.attr('y', -5)
.text(function(d, i) {
if(i === 0) {
// Add dimension label above topmost category
return d.parcatsViewModel.model.dimensions[d.model.dimensionInd].dimensionLabel;
} else {
return null;
}
})
.each(
/** @param {CategoryViewModel} catModel*/
function(catModel) {
Drawing.font(d3.select(this), catModel.parcatsViewModel.labelfont);
});
// Category hover
// categorySelection.select('rect.catrect')
categorySelection.selectAll('rect.bandrect')
.on('mouseover', mouseoverCategoryBand)
.on('mouseout', mouseoutCategory);
// Remove unused categories
categorySelection.exit().remove();
// Setup drag
dimensionSelection.call(d3.behavior.drag()
.origin(function(d) {
return {x: d.x, y: 0};
})
.on('dragstart', dragDimensionStart)
.on('drag', dragDimension)
.on('dragend', dragDimensionEnd));
// Save off selections to view models
traceSelection.each(function(d) {
d.traceSelection = d3.select(this);
d.pathSelection = d3.select(this).selectAll('g.paths').selectAll('path.path');
d.dimensionSelection = d3.select(this).selectAll('g.dimensions').selectAll('g.dimension');
});
// Remove any orphan traces
traceSelection.exit().remove();
}
/**
* Create / update parcat traces
*
* @param {Object} graphDiv
* @param {Object} svg
* @param {Array.} parcatsModels
* @param {Layout} layout
*/
module.exports = function(graphDiv, svg, parcatsModels, layout) {
performPlot(parcatsModels, graphDiv, layout, svg);
};
/**
* Function the returns the key property of an object for use with as D3 join function
* @param d
*/
function key(d) {
return d.key;
}
/** True if a category view model is in the right-most display dimension
* @param {CategoryViewModel} d */
function catInRightDim(d) {
var numDims = d.parcatsViewModel.dimensions.length;
var leftDimInd = d.parcatsViewModel.dimensions[numDims - 1].model.dimensionInd;
return d.model.dimensionInd === leftDimInd;
}
/**
* @param {PathViewModel} a
* @param {PathViewModel} b
*/
function compareRawColor(a, b) {
if(a.model.rawColor > b.model.rawColor) {
return 1;
} else if(a.model.rawColor < b.model.rawColor) {
return -1;
} else {
return 0;
}
}
/**
* Handle path mouseover
* @param {PathViewModel} d
*/
function mouseoverPath(d) {
if(!d.parcatsViewModel.dragDimension) {
// We're not currently dragging
if(d.parcatsViewModel.hoverinfoItems.indexOf('skip') === -1) {
// hoverinfo is not skip, so we at least style the paths and emit interaction events
// Raise path to top
Lib.raiseToTop(this);
stylePathsHover(d3.select(this));
// Emit hover event
var points = buildPointsArrayForPath(d);
var constraints = buildConstraintsForPath(d);
d.parcatsViewModel.graphDiv.emit('plotly_hover', {
points: points, event: d3.event, constraints: constraints
});
// Handle hover label
if(d.parcatsViewModel.hoverinfoItems.indexOf('none') === -1) {
// hoverinfo is a combination of 'count' and 'probability'
// Mouse
var hoverX = d3.mouse(this)[0];
// Label
var gd = d.parcatsViewModel.graphDiv;
var trace = d.parcatsViewModel.trace;
var fullLayout = gd._fullLayout;
var rootBBox = fullLayout._paperdiv.node().getBoundingClientRect();
var graphDivBBox = d.parcatsViewModel.graphDiv.getBoundingClientRect();
// Find path center in path coordinates
var pathCenterX,
pathCenterY,
dimInd;
for(dimInd = 0; dimInd < (d.leftXs.length - 1); dimInd++) {
if(d.leftXs[dimInd] + d.dimWidths[dimInd] - 2 <= hoverX && hoverX <= d.leftXs[dimInd + 1] + 2) {
var leftDim = d.parcatsViewModel.dimensions[dimInd];
var rightDim = d.parcatsViewModel.dimensions[dimInd + 1];
pathCenterX = (leftDim.x + leftDim.width + rightDim.x) / 2;
pathCenterY = (d.topYs[dimInd] + d.topYs[dimInd + 1] + d.height) / 2;
break;
}
}
// Find path center in root coordinates
var hoverCenterX = d.parcatsViewModel.x + pathCenterX;
var hoverCenterY = d.parcatsViewModel.y + pathCenterY;
var textColor = tinycolor.mostReadable(d.model.color, ['black', 'white']);
var count = d.model.count;
var prob = count / d.parcatsViewModel.model.count;
var labels = {
countLabel: count,
probabilityLabel: prob.toFixed(3)
};
// Build hover text
var hovertextParts = [];
if(d.parcatsViewModel.hoverinfoItems.indexOf('count') !== -1) {
hovertextParts.push(['Count:', labels.countLabel].join(' '));
}
if(d.parcatsViewModel.hoverinfoItems.indexOf('probability') !== -1) {
hovertextParts.push(['P:', labels.probabilityLabel].join(' '));
}
var hovertext = hovertextParts.join('
');
var mouseX = d3.mouse(gd)[0];
Fx.loneHover({
trace: trace,
x: hoverCenterX - rootBBox.left + graphDivBBox.left,
y: hoverCenterY - rootBBox.top + graphDivBBox.top,
text: hovertext,
color: d.model.color,
borderColor: 'black',
fontFamily: 'Monaco, "Courier New", monospace',
fontSize: 10,
fontColor: textColor,
idealAlign: mouseX < hoverCenterX ? 'right' : 'left',
hovertemplate: (trace.line || {}).hovertemplate,
hovertemplateLabels: labels,
eventData: [{
data: trace._input,
fullData: trace,
count: count,
probability: prob
}]
}, {
container: fullLayout._hoverlayer.node(),
outerContainer: fullLayout._paper.node(),
gd: gd
});
}
}
}
}
/**
* Handle path mouseout
* @param {PathViewModel} d
*/
function mouseoutPath(d) {
if(!d.parcatsViewModel.dragDimension) {
// We're not currently dragging
stylePathsNoHover(d3.select(this));
// Remove and hover label
Fx.loneUnhover(d.parcatsViewModel.graphDiv._fullLayout._hoverlayer.node());
// Restore path order
d.parcatsViewModel.pathSelection.sort(compareRawColor);
// Emit unhover event
if(d.parcatsViewModel.hoverinfoItems.indexOf('skip') === -1) {
var points = buildPointsArrayForPath(d);
var constraints = buildConstraintsForPath(d);
d.parcatsViewModel.graphDiv.emit('plotly_unhover', {
points: points, event: d3.event, constraints: constraints
});
}
}
}
/**
* Build array of point objects for a path
*
* For use in click/hover events
* @param {PathViewModel} d
*/
function buildPointsArrayForPath(d) {
var points = [];
var curveNumber = getTraceIndex(d.parcatsViewModel);
for(var i = 0; i < d.model.valueInds.length; i++) {
var pointNumber = d.model.valueInds[i];
points.push({
curveNumber: curveNumber,
pointNumber: pointNumber
});
}
return points;
}
/**
* Build constraints object for a path
*
* For use in click/hover events
* @param {PathViewModel} d
*/
function buildConstraintsForPath(d) {
var constraints = {};
var dimensions = d.parcatsViewModel.model.dimensions;
// dimensions
for(var i = 0; i < dimensions.length; i++) {
var dimension = dimensions[i];
var category = dimension.categories[d.model.categoryInds[i]];
constraints[dimension.containerInd] = category.categoryValue;
}
// color
if(d.model.rawColor !== undefined) {
constraints.color = d.model.rawColor;
}
return constraints;
}
/**
* Handle path click
* @param {PathViewModel} d
*/
function clickPath(d) {
if(d.parcatsViewModel.hoverinfoItems.indexOf('skip') === -1) {
// hoverinfo it's skip, so interaction events aren't disabled
var points = buildPointsArrayForPath(d);
var constraints = buildConstraintsForPath(d);
d.parcatsViewModel.graphDiv.emit('plotly_click', {
points: points, event: d3.event, constraints: constraints
});
}
}
function stylePathsNoHover(pathSelection) {
pathSelection
.attr('fill', function(d) {
return d.model.color;
})
.attr('fill-opacity', 0.6)
.attr('stroke', 'lightgray')
.attr('stroke-width', 0.2)
.attr('stroke-opacity', 1.0);
}
function stylePathsHover(pathSelection) {
pathSelection
.attr('fill-opacity', 0.8)
.attr('stroke', function(d) {
return tinycolor.mostReadable(d.model.color, ['black', 'white']);
})
.attr('stroke-width', 0.3);
}
function styleCategoryHover(categorySelection) {
categorySelection
.select('rect.catrect')
.attr('stroke', 'black')
.attr('stroke-width', 2.5);
}
function styleCategoriesNoHover(categorySelection) {
categorySelection
.select('rect.catrect')
.attr('stroke', 'black')
.attr('stroke-width', 1)
.attr('stroke-opacity', 1);
}
function styleBandsHover(bandsSelection) {
bandsSelection
.attr('stroke', 'black')
.attr('stroke-width', 1.5);
}
function styleBandsNoHover(bandsSelection) {
bandsSelection
.attr('stroke', 'black')
.attr('stroke-width', 0.2)
.attr('stroke-opacity', 1.0)
.attr('fill-opacity', 1.0);
}
/**
* Return selection of all paths that pass through the specified category
* @param {CategoryBandViewModel} catBandViewModel
*/
function selectPathsThroughCategoryBandColor(catBandViewModel) {
var allPaths = catBandViewModel.parcatsViewModel.pathSelection;
var dimInd = catBandViewModel.categoryViewModel.model.dimensionInd;
var catInd = catBandViewModel.categoryViewModel.model.categoryInd;
return allPaths
.filter(
/** @param {PathViewModel} pathViewModel */
function(pathViewModel) {
return pathViewModel.model.categoryInds[dimInd] === catInd &&
pathViewModel.model.color === catBandViewModel.color;
});
}
/**
* Perform hover styling for all paths that pass though the specified band element's category
*
* @param {HTMLElement} bandElement
* HTML element for band
*
*/
function styleForCategoryHovermode(bandElement) {
// Get all bands in the current category
var bandSel = d3.select(bandElement.parentNode).selectAll('rect.bandrect');
// Raise and style paths
bandSel.each(function(bvm) {
var paths = selectPathsThroughCategoryBandColor(bvm);
stylePathsHover(paths);
paths.each(function() {
// Raise path to top
Lib.raiseToTop(this);
});
});
// Style category
styleCategoryHover(d3.select(bandElement.parentNode));
}
/**
* Perform hover styling for all paths that pass though the category of the specified band element and share the
* same color
*
* @param {HTMLElement} bandElement
* HTML element for band
*
*/
function styleForColorHovermode(bandElement) {
var bandViewModel = d3.select(bandElement).datum();
var catPaths = selectPathsThroughCategoryBandColor(bandViewModel);
stylePathsHover(catPaths);
catPaths.each(function() {
// Raise path to top
Lib.raiseToTop(this);
});
// Style category for drag
d3.select(bandElement.parentNode)
.selectAll('rect.bandrect')
.filter(function(b) {return b.color === bandViewModel.color;})
.each(function() {
Lib.raiseToTop(this);
styleBandsHover(d3.select(this));
});
}
/**
* @param {HTMLElement} bandElement
* HTML element for band
* @param eventName
* Event name (plotly_hover or plotly_click)
* @param event
* Mouse Event
*/
function emitPointsEventCategoryHovermode(bandElement, eventName, event) {
// Get all bands in the current category
var bandViewModel = d3.select(bandElement).datum();
var categoryModel = bandViewModel.categoryViewModel.model;
var gd = bandViewModel.parcatsViewModel.graphDiv;
var bandSel = d3.select(bandElement.parentNode).selectAll('rect.bandrect');
var points = [];
bandSel.each(function(bvm) {
var paths = selectPathsThroughCategoryBandColor(bvm);
paths.each(function(pathViewModel) {
// Extend points array
Array.prototype.push.apply(points, buildPointsArrayForPath(pathViewModel));
});
});
var constraints = {};
constraints[categoryModel.dimensionInd] = categoryModel.categoryValue;
gd.emit(eventName, {
points: points, event: event, constraints: constraints
});
}
/**
* @param {HTMLElement} bandElement
* HTML element for band
* @param eventName
* Event name (plotly_hover or plotly_click)
* @param event
* Mouse Event
*/
function emitPointsEventColorHovermode(bandElement, eventName, event) {
var bandViewModel = d3.select(bandElement).datum();
var categoryModel = bandViewModel.categoryViewModel.model;
var gd = bandViewModel.parcatsViewModel.graphDiv;
var paths = selectPathsThroughCategoryBandColor(bandViewModel);
var points = [];
paths.each(function(pathViewModel) {
// Extend points array
Array.prototype.push.apply(points, buildPointsArrayForPath(pathViewModel));
});
var constraints = {};
constraints[categoryModel.dimensionInd] = categoryModel.categoryValue;
// color
if(bandViewModel.rawColor !== undefined) {
constraints.color = bandViewModel.rawColor;
}
gd.emit(eventName, {
points: points, event: event, constraints: constraints
});
}
/**
* Create hover label for a band element's category (for use when hoveron === 'category')
*
* @param {ClientRect} rootBBox
* Client bounding box for root of figure
* @param {HTMLElement} bandElement
* HTML element for band
*
*/
function createHoverLabelForCategoryHovermode(gd, rootBBox, bandElement) {
gd._fullLayout._calcInverseTransform(gd);
var scaleX = gd._fullLayout._invScaleX;
var scaleY = gd._fullLayout._invScaleY;
// Selections
var rectSelection = d3.select(bandElement.parentNode).select('rect.catrect');
var rectBoundingBox = rectSelection.node().getBoundingClientRect();
// Models
/** @type {CategoryViewModel} */
var catViewModel = rectSelection.datum();
var parcatsViewModel = catViewModel.parcatsViewModel;
var dimensionModel = parcatsViewModel.model.dimensions[catViewModel.model.dimensionInd];
var trace = parcatsViewModel.trace;
// Positions
var hoverCenterY = rectBoundingBox.top + rectBoundingBox.height / 2;
var hoverCenterX,
hoverLabelIdealAlign;
if(parcatsViewModel.dimensions.length > 1 &&
dimensionModel.displayInd === parcatsViewModel.dimensions.length - 1) {
// right most dimension
hoverCenterX = rectBoundingBox.left;
hoverLabelIdealAlign = 'left';
} else {
hoverCenterX = rectBoundingBox.left + rectBoundingBox.width;
hoverLabelIdealAlign = 'right';
}
var count = catViewModel.model.count;
var catLabel = catViewModel.model.categoryLabel;
var prob = count / catViewModel.parcatsViewModel.model.count;
var labels = {
countLabel: count,
categoryLabel: catLabel,
probabilityLabel: prob.toFixed(3)
};
// Hover label text
var hoverinfoParts = [];
if(catViewModel.parcatsViewModel.hoverinfoItems.indexOf('count') !== -1) {
hoverinfoParts.push(['Count:', labels.countLabel].join(' '));
}
if(catViewModel.parcatsViewModel.hoverinfoItems.indexOf('probability') !== -1) {
hoverinfoParts.push(['P(' + labels.categoryLabel + '):', labels.probabilityLabel].join(' '));
}
var hovertext = hoverinfoParts.join('
');
return {
trace: trace,
x: scaleX * (hoverCenterX - rootBBox.left),
y: scaleY * (hoverCenterY - rootBBox.top),
text: hovertext,
color: 'lightgray',
borderColor: 'black',
fontFamily: 'Monaco, "Courier New", monospace',
fontSize: 12,
fontColor: 'black',
idealAlign: hoverLabelIdealAlign,
hovertemplate: trace.hovertemplate,
hovertemplateLabels: labels,
eventData: [{
data: trace._input,
fullData: trace,
count: count,
category: catLabel,
probability: prob
}]
};
}
/**
* Create hover label for a band element's category (for use when hoveron === 'category')
*
* @param {ClientRect} rootBBox
* Client bounding box for root of figure
* @param {HTMLElement} bandElement
* HTML element for band
*
*/
function createHoverLabelForDimensionHovermode(gd, rootBBox, bandElement) {
var allHoverlabels = [];
d3.select(bandElement.parentNode.parentNode)
.selectAll('g.category')
.select('rect.catrect')
.each(function() {
var bandNode = this;
allHoverlabels.push(createHoverLabelForCategoryHovermode(gd, rootBBox, bandNode));
});
return allHoverlabels;
}
/**
* Create hover labels for a band element's category (for use when hoveron === 'dimension')
*
* @param {ClientRect} rootBBox
* Client bounding box for root of figure
* @param {HTMLElement} bandElement
* HTML element for band
*
*/
function createHoverLabelForColorHovermode(gd, rootBBox, bandElement) {
gd._fullLayout._calcInverseTransform(gd);
var scaleX = gd._fullLayout._invScaleX;
var scaleY = gd._fullLayout._invScaleY;
var bandBoundingBox = bandElement.getBoundingClientRect();
// Models
/** @type {CategoryBandViewModel} */
var bandViewModel = d3.select(bandElement).datum();
var catViewModel = bandViewModel.categoryViewModel;
var parcatsViewModel = catViewModel.parcatsViewModel;
var dimensionModel = parcatsViewModel.model.dimensions[catViewModel.model.dimensionInd];
var trace = parcatsViewModel.trace;
// positions
var hoverCenterY = bandBoundingBox.y + bandBoundingBox.height / 2;
var hoverCenterX,
hoverLabelIdealAlign;
if(parcatsViewModel.dimensions.length > 1 &&
dimensionModel.displayInd === parcatsViewModel.dimensions.length - 1) {
// right most dimension
hoverCenterX = bandBoundingBox.left;
hoverLabelIdealAlign = 'left';
} else {
hoverCenterX = bandBoundingBox.left + bandBoundingBox.width;
hoverLabelIdealAlign = 'right';
}
// Labels
var catLabel = catViewModel.model.categoryLabel;
// Counts
var totalCount = bandViewModel.parcatsViewModel.model.count;
var bandColorCount = 0;
bandViewModel.categoryViewModel.bands.forEach(function(b) {
if(b.color === bandViewModel.color) {
bandColorCount += b.count;
}
});
var catCount = catViewModel.model.count;
var colorCount = 0;
parcatsViewModel.pathSelection.each(
/** @param {PathViewModel} pathViewModel */
function(pathViewModel) {
if(pathViewModel.model.color === bandViewModel.color) {
colorCount += pathViewModel.model.count;
}
});
var pColorAndCat = bandColorCount / totalCount;
var pCatGivenColor = bandColorCount / colorCount;
var pColorGivenCat = bandColorCount / catCount;
var labels = {
countLabel: totalCount,
categoryLabel: catLabel,
probabilityLabel: pColorAndCat.toFixed(3)
};
// Hover label text
var hoverinfoParts = [];
if(catViewModel.parcatsViewModel.hoverinfoItems.indexOf('count') !== -1) {
hoverinfoParts.push(['Count:', labels.countLabel].join(' '));
}
if(catViewModel.parcatsViewModel.hoverinfoItems.indexOf('probability') !== -1) {
hoverinfoParts.push('P(color ∩ ' + catLabel + '): ' + labels.probabilityLabel);
hoverinfoParts.push('P(' + catLabel + ' | color): ' + pCatGivenColor.toFixed(3));
hoverinfoParts.push('P(color | ' + catLabel + '): ' + pColorGivenCat.toFixed(3));
}
var hovertext = hoverinfoParts.join('
');
// Compute text color
var textColor = tinycolor.mostReadable(bandViewModel.color, ['black', 'white']);
return {
trace: trace,
x: scaleX * (hoverCenterX - rootBBox.left),
y: scaleY * (hoverCenterY - rootBBox.top),
// name: 'NAME',
text: hovertext,
color: bandViewModel.color,
borderColor: 'black',
fontFamily: 'Monaco, "Courier New", monospace',
fontColor: textColor,
fontSize: 10,
idealAlign: hoverLabelIdealAlign,
hovertemplate: trace.hovertemplate,
hovertemplateLabels: labels,
eventData: [{
data: trace._input,
fullData: trace,
category: catLabel,
count: totalCount,
probability: pColorAndCat,
categorycount: catCount,
colorcount: colorCount,
bandcolorcount: bandColorCount
}]
};
}
/**
* Handle dimension mouseover
* @param {CategoryBandViewModel} bandViewModel
*/
function mouseoverCategoryBand(bandViewModel) {
if(!bandViewModel.parcatsViewModel.dragDimension) {
// We're not currently dragging
if(bandViewModel.parcatsViewModel.hoverinfoItems.indexOf('skip') === -1) {
// hoverinfo is not skip, so we at least style the bands and emit interaction events
// Mouse
var mouseY = d3.mouse(this)[1];
if(mouseY < -1) {
// Hover is above above the category rectangle (probably the dimension title text)
return;
}
var gd = bandViewModel.parcatsViewModel.graphDiv;
var fullLayout = gd._fullLayout;
var rootBBox = fullLayout._paperdiv.node().getBoundingClientRect();
var hoveron = bandViewModel.parcatsViewModel.hoveron;
/** @type {HTMLElement} */
var bandElement = this;
// Handle style and events
if(hoveron === 'color') {
styleForColorHovermode(bandElement);
emitPointsEventColorHovermode(bandElement, 'plotly_hover', d3.event);
} else {
styleForCategoryHovermode(bandElement);
emitPointsEventCategoryHovermode(bandElement, 'plotly_hover', d3.event);
}
// Handle hover label
if(bandViewModel.parcatsViewModel.hoverinfoItems.indexOf('none') === -1) {
var hoverItems;
if(hoveron === 'category') {
hoverItems = createHoverLabelForCategoryHovermode(gd, rootBBox, bandElement);
} else if(hoveron === 'color') {
hoverItems = createHoverLabelForColorHovermode(gd, rootBBox, bandElement);
} else if(hoveron === 'dimension') {
hoverItems = createHoverLabelForDimensionHovermode(gd, rootBBox, bandElement);
}
if(hoverItems) {
Fx.loneHover(hoverItems, {
container: fullLayout._hoverlayer.node(),
outerContainer: fullLayout._paper.node(),
gd: gd
});
}
}
}
}
}
/**
* Handle dimension mouseover
* @param {CategoryBandViewModel} bandViewModel
*/
function mouseoutCategory(bandViewModel) {
var parcatsViewModel = bandViewModel.parcatsViewModel;
if(!parcatsViewModel.dragDimension) {
// We're not dragging anything
// Reset unhovered styles
stylePathsNoHover(parcatsViewModel.pathSelection);
styleCategoriesNoHover(parcatsViewModel.dimensionSelection.selectAll('g.category'));
styleBandsNoHover(parcatsViewModel.dimensionSelection.selectAll('g.category').selectAll('rect.bandrect'));
// Remove hover label
Fx.loneUnhover(parcatsViewModel.graphDiv._fullLayout._hoverlayer.node());
// Restore path order
parcatsViewModel.pathSelection.sort(compareRawColor);
// Emit unhover event
if(parcatsViewModel.hoverinfoItems.indexOf('skip') === -1) {
var hoveron = bandViewModel.parcatsViewModel.hoveron;
var bandElement = this;
// Handle style and events
if(hoveron === 'color') {
emitPointsEventColorHovermode(bandElement, 'plotly_unhover', d3.event);
} else {
emitPointsEventCategoryHovermode(bandElement, 'plotly_unhover', d3.event);
}
}
}
}
/**
* Handle dimension drag start
* @param {DimensionViewModel} d
*/
function dragDimensionStart(d) {
// Check if dragging is supported
if(d.parcatsViewModel.arrangement === 'fixed') {
return;
}
// Save off initial drag indexes for dimension
d.dragDimensionDisplayInd = d.model.displayInd;
d.initialDragDimensionDisplayInds = d.parcatsViewModel.model.dimensions.map(function(d) {return d.displayInd;});
d.dragHasMoved = false;
// Check for category hit
d.dragCategoryDisplayInd = null;
d3.select(this)
.selectAll('g.category')
.select('rect.catrect')
.each(
/** @param {CategoryViewModel} catViewModel */
function(catViewModel) {
var catMouseX = d3.mouse(this)[0];
var catMouseY = d3.mouse(this)[1];
if(-2 <= catMouseX && catMouseX <= catViewModel.width + 2 &&
-2 <= catMouseY && catMouseY <= catViewModel.height + 2) {
// Save off initial drag indexes for categories
d.dragCategoryDisplayInd = catViewModel.model.displayInd;
d.initialDragCategoryDisplayInds = d.model.categories.map(function(c) {
return c.displayInd;
});
// Initialize categories dragY to be the current y position
catViewModel.model.dragY = catViewModel.y;
// Raise category
Lib.raiseToTop(this.parentNode);
// Get band element
d3.select(this.parentNode)
.selectAll('rect.bandrect')
/** @param {CategoryBandViewModel} bandViewModel */
.each(function(bandViewModel) {
if(bandViewModel.y < catMouseY && catMouseY <= bandViewModel.y + bandViewModel.height) {
d.potentialClickBand = this;
}
});
}
});
// Update toplevel drag dimension
d.parcatsViewModel.dragDimension = d;
// Remove hover label if any
Fx.loneUnhover(d.parcatsViewModel.graphDiv._fullLayout._hoverlayer.node());
}
/**
* Handle dimension drag
* @param {DimensionViewModel} d
*/
function dragDimension(d) {
// Check if dragging is supported
if(d.parcatsViewModel.arrangement === 'fixed') {
return;
}
d.dragHasMoved = true;
if(d.dragDimensionDisplayInd === null) {
return;
}
var dragDimInd = d.dragDimensionDisplayInd;
var prevDimInd = dragDimInd - 1;
var nextDimInd = dragDimInd + 1;
var dragDimension = d.parcatsViewModel
.dimensions[dragDimInd];
// Update category
if(d.dragCategoryDisplayInd !== null) {
var dragCategory = dragDimension.categories[d.dragCategoryDisplayInd];
// Update dragY by dy
dragCategory.model.dragY += d3.event.dy;
var categoryY = dragCategory.model.dragY;
// Check for category drag swaps
var catDisplayInd = dragCategory.model.displayInd;
var dimCategoryViews = dragDimension.categories;
var catAbove = dimCategoryViews[catDisplayInd - 1];
var catBelow = dimCategoryViews[catDisplayInd + 1];
// Check for overlap above
if(catAbove !== undefined) {
if(categoryY < (catAbove.y + catAbove.height / 2.0)) {
// Swap display inds
dragCategory.model.displayInd = catAbove.model.displayInd;
catAbove.model.displayInd = catDisplayInd;
}
}
if(catBelow !== undefined) {
if((categoryY + dragCategory.height) > (catBelow.y + catBelow.height / 2.0)) {
// Swap display inds
dragCategory.model.displayInd = catBelow.model.displayInd;
catBelow.model.displayInd = catDisplayInd;
}
}
// Update category drag display index
d.dragCategoryDisplayInd = dragCategory.model.displayInd;
}
// Update dimension position
if(d.dragCategoryDisplayInd === null || d.parcatsViewModel.arrangement === 'freeform') {
dragDimension.model.dragX = d3.event.x;
// Check for dimension swaps
var prevDimension = d.parcatsViewModel.dimensions[prevDimInd];
var nextDimension = d.parcatsViewModel.dimensions[nextDimInd];
if(prevDimension !== undefined) {
if(dragDimension.model.dragX < (prevDimension.x + prevDimension.width)) {
// Swap display inds
dragDimension.model.displayInd = prevDimension.model.displayInd;
prevDimension.model.displayInd = dragDimInd;
}
}
if(nextDimension !== undefined) {
if((dragDimension.model.dragX + dragDimension.width) > nextDimension.x) {
// Swap display inds
dragDimension.model.displayInd = nextDimension.model.displayInd;
nextDimension.model.displayInd = d.dragDimensionDisplayInd;
}
}
// Update drag display index
d.dragDimensionDisplayInd = dragDimension.model.displayInd;
}
// Update view models
updateDimensionViewModels(d.parcatsViewModel);
updatePathViewModels(d.parcatsViewModel);
// Update svg geometry
updateSvgCategories(d.parcatsViewModel);
updateSvgPaths(d.parcatsViewModel);
}
/**
* Handle dimension drag end
* @param {DimensionViewModel} d
*/
function dragDimensionEnd(d) {
// Check if dragging is supported
if(d.parcatsViewModel.arrangement === 'fixed') {
return;
}
if(d.dragDimensionDisplayInd === null) {
return;
}
d3.select(this).selectAll('text').attr('font-weight', 'normal');
// Compute restyle command
// -----------------------
var restyleData = {};
var traceInd = getTraceIndex(d.parcatsViewModel);
// ### Handle dimension reordering ###
var finalDragDimensionDisplayInds = d.parcatsViewModel.model.dimensions.map(function(d) {return d.displayInd;});
var anyDimsReordered = d.initialDragDimensionDisplayInds.some(function(initDimDisplay, dimInd) {
return initDimDisplay !== finalDragDimensionDisplayInds[dimInd];
});
if(anyDimsReordered) {
finalDragDimensionDisplayInds.forEach(function(finalDimDisplay, dimInd) {
var containerInd = d.parcatsViewModel.model.dimensions[dimInd].containerInd;
restyleData['dimensions[' + containerInd + '].displayindex'] = finalDimDisplay;
});
}
// ### Handle category reordering ###
var anyCatsReordered = false;
if(d.dragCategoryDisplayInd !== null) {
var finalDragCategoryDisplayInds = d.model.categories.map(function(c) {
return c.displayInd;
});
anyCatsReordered = d.initialDragCategoryDisplayInds.some(function(initCatDisplay, catInd) {
return initCatDisplay !== finalDragCategoryDisplayInds[catInd];
});
if(anyCatsReordered) {
// Sort a shallow copy of the category models by display index
var sortedCategoryModels = d.model.categories.slice().sort(
function(a, b) { return a.displayInd - b.displayInd; });
// Get new categoryarray and ticktext values
var newCategoryArray = sortedCategoryModels.map(function(v) { return v.categoryValue; });
var newCategoryLabels = sortedCategoryModels.map(function(v) { return v.categoryLabel; });
restyleData['dimensions[' + d.model.containerInd + '].categoryarray'] = [newCategoryArray];
restyleData['dimensions[' + d.model.containerInd + '].ticktext'] = [newCategoryLabels];
restyleData['dimensions[' + d.model.containerInd + '].categoryorder'] = 'array';
}
}
// Handle potential click event
// ----------------------------
if(d.parcatsViewModel.hoverinfoItems.indexOf('skip') === -1) {
if(!d.dragHasMoved && d.potentialClickBand) {
if(d.parcatsViewModel.hoveron === 'color') {
emitPointsEventColorHovermode(d.potentialClickBand, 'plotly_click', d3.event.sourceEvent);
} else {
emitPointsEventCategoryHovermode(d.potentialClickBand, 'plotly_click', d3.event.sourceEvent);
}
}
}
// Nullify drag states
// -------------------
d.model.dragX = null;
if(d.dragCategoryDisplayInd !== null) {
var dragCategory = d.parcatsViewModel
.dimensions[d.dragDimensionDisplayInd]
.categories[d.dragCategoryDisplayInd];
dragCategory.model.dragY = null;
d.dragCategoryDisplayInd = null;
}
d.dragDimensionDisplayInd = null;
d.parcatsViewModel.dragDimension = null;
d.dragHasMoved = null;
d.potentialClickBand = null;
// Update view models
// ------------------
updateDimensionViewModels(d.parcatsViewModel);
updatePathViewModels(d.parcatsViewModel);
// Perform transition
// ------------------
var transition = d3.transition()
.duration(300)
.ease('cubic-in-out');
transition
.each(function() {
updateSvgCategories(d.parcatsViewModel, true);
updateSvgPaths(d.parcatsViewModel, true);
})
.each('end', function() {
if(anyDimsReordered || anyCatsReordered) {
// Perform restyle if the order of categories or dimensions changed
Plotly.restyle(d.parcatsViewModel.graphDiv, restyleData, [traceInd]);
}
});
}
/**
*
* @param {ParcatsViewModel} parcatsViewModel
*/
function getTraceIndex(parcatsViewModel) {
var traceInd;
var allTraces = parcatsViewModel.graphDiv._fullData;
for(var i = 0; i < allTraces.length; i++) {
if(parcatsViewModel.key === allTraces[i].uid) {
traceInd = i;
break;
}
}
return traceInd;
}
/** Update the svg paths for view model
* @param {ParcatsViewModel} parcatsViewModel
* @param {boolean} hasTransition Whether to update element with transition
*/
function updateSvgPaths(parcatsViewModel, hasTransition) {
if(hasTransition === undefined) {
hasTransition = false;
}
function transition(selection) {
return hasTransition ? selection.transition() : selection;
}
// Update binding
parcatsViewModel.pathSelection.data(function(d) {
return d.paths;
}, key);
// Update paths
transition(parcatsViewModel.pathSelection).attr('d', function(d) {
return d.svgD;
});
}
/** Update the svg paths for view model
* @param {ParcatsViewModel} parcatsViewModel
* @param {boolean} hasTransition Whether to update element with transition
*/
function updateSvgCategories(parcatsViewModel, hasTransition) {
if(hasTransition === undefined) {
hasTransition = false;
}
function transition(selection) {
return hasTransition ? selection.transition() : selection;
}
// Update binding
parcatsViewModel.dimensionSelection
.data(function(d) {
return d.dimensions;
}, key);
var categorySelection = parcatsViewModel.dimensionSelection
.selectAll('g.category')
.data(function(d) {return d.categories;}, key);
// Update dimension position
transition(parcatsViewModel.dimensionSelection)
.attr('transform', function(d) {
return strTranslate(d.x, 0);
});
// Update category position
transition(categorySelection)
.attr('transform', function(d) {
return strTranslate(0, d.y);
});
var dimLabelSelection = categorySelection.select('.dimlabel');
// ### Update dimension label
// Only the top-most display category should have the dimension label
dimLabelSelection
.text(function(d, i) {
if(i === 0) {
// Add dimension label above topmost category
return d.parcatsViewModel.model.dimensions[d.model.dimensionInd].dimensionLabel;
} else {
return null;
}
});
// Update category label
// Categories in the right-most display dimension have their labels on
// the right, all others on the left
var catLabelSelection = categorySelection.select('.catlabel');
catLabelSelection
.attr('text-anchor',
function(d) {
if(catInRightDim(d)) {
// Place label to the right of category
return 'start';
} else {
// Place label to the left of category
return 'end';
}
})
.attr('x',
function(d) {
if(catInRightDim(d)) {
// Place label to the right of category
return d.width + 5;
} else {
// Place label to the left of category
return -5;
}
})
.each(function(d) {
// Update attriubutes of elements
var newX;
var newAnchor;
if(catInRightDim(d)) {
// Place label to the right of category
newX = d.width + 5;
newAnchor = 'start';
} else {
// Place label to the left of category
newX = -5;
newAnchor = 'end';
}
d3.select(this)
.selectAll('tspan')
.attr('x', newX)
.attr('text-anchor', newAnchor);
});
// Update bands
// Initialize color band rects
var bandSelection = categorySelection
.selectAll('rect.bandrect')
.data(
/** @param {CategoryViewModel} catViewModel*/
function(catViewModel) {
return catViewModel.bands;
}, key);
var bandsSelectionEnter = bandSelection.enter()
.append('rect')
.attr('class', 'bandrect')
.attr('cursor', 'move')
.attr('stroke-opacity', 0)
.attr('fill', function(d) {
return d.color;
})
.attr('fill-opacity', 0);
bandSelection
.attr('fill', function(d) {
return d.color;
})
.attr('width', function(d) {
return d.width;
})
.attr('height', function(d) {
return d.height;
})
.attr('y', function(d) {
return d.y;
});
styleBandsNoHover(bandsSelectionEnter);
// Raise bands to the top
bandSelection.each(function() {Lib.raiseToTop(this);});
// Remove unused bands
bandSelection.exit().remove();
}
/**
* Create a ParcatsViewModel traces
* @param {Object} graphDiv
* Top-level graph div element
* @param {Layout} layout
* SVG layout object
* @param {Array.} wrappedParcatsModel
* Wrapped ParcatsModel for this trace
* @return {ParcatsViewModel}
*/
function createParcatsViewModel(graphDiv, layout, wrappedParcatsModel) {
// Unwrap model
var parcatsModel = wrappedParcatsModel[0];
// Compute margin
var margin = layout.margin || {l: 80, r: 80, t: 100, b: 80};
// Compute pixel position/extents
var trace = parcatsModel.trace;
var domain = trace.domain;
var figureWidth = layout.width;
var figureHeight = layout.height;
var traceWidth = Math.floor(figureWidth * (domain.x[1] - domain.x[0]));
var traceHeight = Math.floor(figureHeight * (domain.y[1] - domain.y[0]));
var traceX = domain.x[0] * figureWidth + margin.l;
var traceY = layout.height - domain.y[1] * layout.height + margin.t;
// Handle path shape
// -----------------
var pathShape = trace.line.shape;
// Handle hover info
// -----------------
var hoverinfoItems;
if(trace.hoverinfo === 'all') {
hoverinfoItems = ['count', 'probability'];
} else {
hoverinfoItems = (trace.hoverinfo || '').split('+');
}
// Construct parcatsViewModel
// --------------------------
var parcatsViewModel = {
trace: trace,
key: trace.uid,
model: parcatsModel,
x: traceX,
y: traceY,
width: traceWidth,
height: traceHeight,
hoveron: trace.hoveron,
hoverinfoItems: hoverinfoItems,
arrangement: trace.arrangement,
bundlecolors: trace.bundlecolors,
sortpaths: trace.sortpaths,
labelfont: trace.labelfont,
categorylabelfont: trace.tickfont,
pathShape: pathShape,
dragDimension: null,
margin: margin,
paths: [],
dimensions: [],
graphDiv: graphDiv,
traceSelection: null,
pathSelection: null,
dimensionSelection: null
};
// Update dimension view models if we have at least 1 dimension
if(parcatsModel.dimensions) {
updateDimensionViewModels(parcatsViewModel);
// Update path view models if we have at least 2 dimensions
updatePathViewModels(parcatsViewModel);
}
// Inside a categories view model
return parcatsViewModel;
}
/**
* Build the SVG string to represents a parallel categories path
* @param {Array.} leftXPositions
* Array of the x positions of the left edge of each dimension (in display order)
* @param {Array.} pathYs
* Array of the y positions of the top of the path at each dimension (in display order)
* @param {Array.} dimWidths
* Array of the widths of each dimension in display order
* @param {Number} pathHeight
* The height of the path in pixels
* @param {Number} curvature
* The curvature factor for the path. 0 results in a straight line and values greater than zero result in curved paths
* @return {string}
*/
function buildSvgPath(leftXPositions, pathYs, dimWidths, pathHeight, curvature) {
// Compute the x midpoint of each path segment
var xRefPoints1 = [];
var xRefPoints2 = [];
var refInterpolator;
var d;
for(d = 0; d < dimWidths.length - 1; d++) {
refInterpolator = interpolateNumber(dimWidths[d] + leftXPositions[d], leftXPositions[d + 1]);
xRefPoints1.push(refInterpolator(curvature));
xRefPoints2.push(refInterpolator(1 - curvature));
}
// Move to top of path on left edge of left-most category
var svgD = 'M ' + leftXPositions[0] + ',' + pathYs[0];
// Horizontal line to right edge
svgD += 'l' + dimWidths[0] + ',0 ';
// Horizontal line to right edge
for(d = 1; d < dimWidths.length; d++) {
// Curve to left edge of category
svgD += 'C' + xRefPoints1[d - 1] + ',' + pathYs[d - 1] +
' ' + xRefPoints2[d - 1] + ',' + pathYs[d] +
' ' + leftXPositions[d] + ',' + pathYs[d];
// svgD += 'L' + leftXPositions[d] + ',' + pathYs[d];
// Horizontal line to right edge
svgD += 'l' + dimWidths[d] + ',0 ';
}
// Line down
svgD += 'l' + '0,' + pathHeight + ' ';
// Line to left edge of right-most category
svgD += 'l -' + dimWidths[dimWidths.length - 1] + ',0 ';
for(d = dimWidths.length - 2; d >= 0; d--) {
// Curve to right edge of category
svgD += 'C' + xRefPoints2[d] + ',' + (pathYs[d + 1] + pathHeight) +
' ' + xRefPoints1[d] + ',' + (pathYs[d] + pathHeight) +
' ' + (leftXPositions[d] + dimWidths[d]) + ',' + (pathYs[d] + pathHeight);
// svgD += 'L' + (leftXPositions[d] + dimWidths[d]) + ',' + (pathYs[d] + pathHeight);
// Horizontal line to right edge
svgD += 'l-' + dimWidths[d] + ',0 ';
}
// Close path
svgD += 'Z';
return svgD;
}
/**
* Update the path view models based on the dimension view models in a ParcatsViewModel
*
* @param {ParcatsViewModel} parcatsViewModel
* View model for trace
*/
function updatePathViewModels(parcatsViewModel) {
// Initialize an array of the y position of the top of the next path to be added to each category.
//
// nextYPositions[d][c] is the y position of the next path through category with index c of dimension with index d
var dimensionViewModels = parcatsViewModel.dimensions;
var parcatsModel = parcatsViewModel.model;
var nextYPositions = dimensionViewModels.map(
function(d) {
return d.categories.map(
function(c) {
return c.y;
});
});
// Array from category index to category display index for each true dimension index
var catToDisplayIndPerDim = parcatsViewModel.model.dimensions.map(
function(d) {
return d.categories.map(function(c) {return c.displayInd;});
});
// Array from true dimension index to dimension display index
var dimToDisplayInd = parcatsViewModel.model.dimensions.map(function(d) {return d.displayInd;});
var displayToDimInd = parcatsViewModel.dimensions.map(function(d) {return d.model.dimensionInd;});
// Array of the x position of the left edge of the rectangles for each dimension
var leftXPositions = dimensionViewModels.map(
function(d) {
return d.x;
});
// Compute dimension widths
var dimWidths = dimensionViewModels.map(function(d) {return d.width;});
// Build sorted Array of PathModel objects
var pathModels = [];
for(var p in parcatsModel.paths) {
if(parcatsModel.paths.hasOwnProperty(p)) {
pathModels.push(parcatsModel.paths[p]);
}
}
// Compute category display inds to use for sorting paths
function pathDisplayCategoryInds(pathModel) {
var dimensionInds = pathModel.categoryInds.map(function(catInd, dimInd) {return catToDisplayIndPerDim[dimInd][catInd];});
var displayInds = displayToDimInd.map(function(dimInd) {
return dimensionInds[dimInd];
});
return displayInds;
}
// Sort in ascending order by display index array
pathModels.sort(function(v1, v2) {
// Build display inds for each path
var sortArray1 = pathDisplayCategoryInds(v1);
var sortArray2 = pathDisplayCategoryInds(v2);
// Handle path sort order
if(parcatsViewModel.sortpaths === 'backward') {
sortArray1.reverse();
sortArray2.reverse();
}
// Append the first value index of the path to break ties
sortArray1.push(v1.valueInds[0]);
sortArray2.push(v2.valueInds[0]);
// Handle color bundling
if(parcatsViewModel.bundlecolors) {
// Prepend sort array with the raw color value
sortArray1.unshift(v1.rawColor);
sortArray2.unshift(v2.rawColor);
}
// colors equal, sort by display categories
if(sortArray1 < sortArray2) {
return -1;
}
if(sortArray1 > sortArray2) {
return 1;
}
return 0;
});
// Create path models
var pathViewModels = new Array(pathModels.length);
var totalCount = dimensionViewModels[0].model.count;
var totalHeight = dimensionViewModels[0].categories
.map(function(c) { return c.height; })
.reduce(function(v1, v2) { return v1 + v2; });
for(var pathNumber = 0; pathNumber < pathModels.length; pathNumber++) {
var pathModel = pathModels[pathNumber];
var pathHeight;
if(totalCount > 0) {
pathHeight = totalHeight * (pathModel.count / totalCount);
} else {
pathHeight = 0;
}
// Build path y coords
var pathYs = new Array(nextYPositions.length);
for(var d = 0; d < pathModel.categoryInds.length; d++) {
var catInd = pathModel.categoryInds[d];
var catDisplayInd = catToDisplayIndPerDim[d][catInd];
var dimDisplayInd = dimToDisplayInd[d];
// Update next y position
pathYs[dimDisplayInd] = nextYPositions[dimDisplayInd][catDisplayInd];
nextYPositions[dimDisplayInd][catDisplayInd] += pathHeight;
// Update category color information
var catViewModle = parcatsViewModel.dimensions[dimDisplayInd].categories[catDisplayInd];
var numBands = catViewModle.bands.length;
var lastCatBand = catViewModle.bands[numBands - 1];
if(lastCatBand === undefined || pathModel.rawColor !== lastCatBand.rawColor) {
// Create a new band
var bandY = lastCatBand === undefined ? 0 : lastCatBand.y + lastCatBand.height;
catViewModle.bands.push({
key: bandY,
color: pathModel.color,
rawColor: pathModel.rawColor,
height: pathHeight,
width: catViewModle.width,
count: pathModel.count,
y: bandY,
categoryViewModel: catViewModle,
parcatsViewModel: parcatsViewModel
});
} else {
// Extend current band
var currentBand = catViewModle.bands[numBands - 1];
currentBand.height += pathHeight;
currentBand.count += pathModel.count;
}
}
// build svg path
var svgD;
if(parcatsViewModel.pathShape === 'hspline') {
svgD = buildSvgPath(leftXPositions, pathYs, dimWidths, pathHeight, 0.5);
} else {
svgD = buildSvgPath(leftXPositions, pathYs, dimWidths, pathHeight, 0);
}
pathViewModels[pathNumber] = {
key: pathModel.valueInds[0],
model: pathModel,
height: pathHeight,
leftXs: leftXPositions,
topYs: pathYs,
dimWidths: dimWidths,
svgD: svgD,
parcatsViewModel: parcatsViewModel
};
}
parcatsViewModel.paths = pathViewModels;
// * @property key
// * Unique key for this model
// * @property {PathModel} model
// * Source path model
// * @property {Number} height
// * Height of this path (pixels)
// * @property {String} svgD
// * SVG path "d" attribute string
}
/**
* Update the dimension view models based on the dimension models in a ParcatsViewModel
*
* @param {ParcatsViewModel} parcatsViewModel
* View model for trace
*/
function updateDimensionViewModels(parcatsViewModel) {
// Compute dimension ordering
var dimensionsIndInfo = parcatsViewModel.model.dimensions.map(function(d) {
return {displayInd: d.displayInd, dimensionInd: d.dimensionInd};
});
dimensionsIndInfo.sort(function(a, b) {
return a.displayInd - b.displayInd;
});
var dimensions = [];
for(var displayInd in dimensionsIndInfo) {
var dimensionInd = dimensionsIndInfo[displayInd].dimensionInd;
var dimModel = parcatsViewModel.model.dimensions[dimensionInd];
dimensions.push(createDimensionViewModel(parcatsViewModel, dimModel));
}
parcatsViewModel.dimensions = dimensions;
}
/**
* Create a parcats DimensionViewModel
*
* @param {ParcatsViewModel} parcatsViewModel
* View model for trace
* @param {DimensionModel} dimensionModel
* @return {DimensionViewModel}
*/
function createDimensionViewModel(parcatsViewModel, dimensionModel) {
// Compute dimension x position
var categoryLabelPad = 40;
var dimWidth = 16;
var numDimensions = parcatsViewModel.model.dimensions.length;
var displayInd = dimensionModel.displayInd;
// Compute x coordinate values
var dimDx;
var dimX0;
var dimX;
if(numDimensions > 1) {
dimDx = (parcatsViewModel.width - 2 * categoryLabelPad - dimWidth) / (numDimensions - 1);
} else {
dimDx = 0;
}
dimX0 = categoryLabelPad;
dimX = dimX0 + dimDx * displayInd;
// Compute categories
var categories = [];
var maxCats = parcatsViewModel.model.maxCats;
var numCats = dimensionModel.categories.length;
var catSpacing = 8;
var totalCount = dimensionModel.count;
var totalHeight = parcatsViewModel.height - catSpacing * (maxCats - 1);
var nextCatHeight;
var nextCatModel;
var nextCat;
var catInd;
var catDisplayInd;
// Compute starting Y offset
var nextCatY = (maxCats - numCats) * catSpacing / 2.0;
// Compute category ordering
var categoryIndInfo = dimensionModel.categories.map(function(c) {
return {displayInd: c.displayInd, categoryInd: c.categoryInd};
});
categoryIndInfo.sort(function(a, b) {
return a.displayInd - b.displayInd;
});
for(catDisplayInd = 0; catDisplayInd < numCats; catDisplayInd++) {
catInd = categoryIndInfo[catDisplayInd].categoryInd;
nextCatModel = dimensionModel.categories[catInd];
if(totalCount > 0) {
nextCatHeight = (nextCatModel.count / totalCount) * totalHeight;
} else {
nextCatHeight = 0;
}
nextCat = {
key: nextCatModel.valueInds[0],
model: nextCatModel,
width: dimWidth,
height: nextCatHeight,
y: nextCatModel.dragY !== null ? nextCatModel.dragY : nextCatY,
bands: [],
parcatsViewModel: parcatsViewModel
};
nextCatY = nextCatY + nextCatHeight + catSpacing;
categories.push(nextCat);
}
return {
key: dimensionModel.dimensionInd,
x: dimensionModel.dragX !== null ? dimensionModel.dragX : dimX,
y: 0,
width: dimWidth,
model: dimensionModel,
categories: categories,
parcatsViewModel: parcatsViewModel,
dragCategoryDisplayInd: null,
dragDimensionDisplayInd: null,
initialDragDimensionDisplayInds: null,
initialDragCategoryDisplayInds: null,
dragHasMoved: null,
potentialClickBand: null
};
}
// JSDoc typedefs
// ==============
/**
* @typedef {Object} Layout
* Object containing svg layout information
*
* @property {Number} width (pixels)
* Usable width for Figure (after margins are removed)
* @property {Number} height (pixels)
* Usable height for Figure (after margins are removed)
* @property {Margin} margin
* Margin around the Figure (pixels)
*/
/**
* @typedef {Object} Margin
* Object containing padding information in pixels
*
* @property {Number} t
* Top margin
* @property {Number} r
* Right margin
* @property {Number} b
* Bottom margin
* @property {Number} l
* Left margin
*/
/**
* @typedef {Object} Font
* Object containing font information
*
* @property {Number} size: Font size
* @property {String} color: Font color
* @property {String} family: Font family
*/
/**
* @typedef {Object} ParcatsViewModel
* Object containing calculated parcats view information
*
* These are quantities that require Layout information to calculate
* @property key
* Unique key for this model
* @property {ParcatsModel} model
* Source parcats model
* @property {Array.} dimensions
* Array of dimension view models
* @property {Number} width
* Width for this trace (pixels)
* @property {Number} height
* Height for this trace (pixels)
* @property {Number} x
* X position of this trace with respect to the Figure (pixels)
* @property {Number} y
* Y position of this trace with respect to the Figure (pixels)
* @property {String} hoveron
* Hover interaction mode. One of: 'category', 'color', or 'dimension'
* @property {Array.} hoverinfoItems
* Info to display on hover. Array with a combination of 'counts' and/or 'probabilities', or 'none', or 'skip'
* @property {String} arrangement
* Category arrangement. One of: 'perpendicular', 'freeform', or 'fixed'
* @property {Boolean} bundlecolors
* Whether paths should be sorted so that like colors are bundled together as they pass through categories
* @property {String} sortpaths
* If 'forward' then sort paths based on dimensions from left to right. If 'backward' sort based on dimensions
* from right to left
* @property {Font} labelfont
* Font for the dimension labels
* @property {Font} categorylabelfont
* Font for the category labels
* @property {String} pathShape
* The shape of the paths. Either 'linear' or 'hspline'.
* @property {DimensionViewModel|null} dragDimension
* Dimension currently being dragged. Null if no drag in progress
* @property {Margin} margin
* Margin around the Figure
* @property {Object} graphDiv
* Top-level graph div element
* @property {Object} traceSelection
* D3 selection of this view models trace group element
* @property {Object} pathSelection
* D3 selection of this view models path elements
* @property {Object} dimensionSelection
* D3 selection of this view models dimension group element
*/
/**
* @typedef {Object} DimensionViewModel
* Object containing calculated parcats dimension view information
*
* These are quantities that require Layout information to calculate
* @property key
* Unique key for this model
* @property {DimensionModel} model
* Source dimension model
* @property {Number} x
* X position of the center of this dimension with respect to the Figure (pixels)
* @property {Number} y
* Y position of the top of this dimension with respect to the Figure (pixels)
* @property {Number} width
* Width of categories in this dimension (pixels)
* @property {ParcatsViewModel} parcatsViewModel
* The parent trace's view model
* @property {Array.} categories
* Dimensions category view models
* @property {Number|null} dragCategoryDisplayInd
* Display index of category currently being dragged. null if no category is being dragged
* @property {Number|null} dragDimensionDisplayInd
* Display index of the dimension being dragged. null if no dimension is being dragged
* @property {Array.|null} initialDragDimensionDisplayInds
* Dimensions display indexes at the beginning of the current drag. null if no dimension is being dragged
* @property {Array.|null} initialDragCategoryDisplayInds
* Category display indexes for the at the beginning of the current drag. null if no category is being dragged
* @property {HTMLElement} potentialClickBand
* Band under mouse when current drag began. If no drag movement takes place then a click will be emitted for this
* band. Null if not drag in progress.
* @property {Boolean} dragHasMoved
* True if there is an active drag and the drag has moved. If drag doesn't move before being ended then
* this may be interpreted as a click. Null if no drag in progress
*/
/**
* @typedef {Object} CategoryViewModel
* Object containing calculated parcats category view information
*
* These are quantities that require Layout information to calculate
* @property key
* Unique key for this model
* @property {CategoryModel} model
* Source category model
* @property {Number} width
* Width for this category (pixels)
* @property {Number} height
* Height for this category (pixels)
* @property {Number} y
* Y position of this cateogry with respect to the Figure (pixels)
* @property {Array.} bands
* Array of color bands inside the category
* @property {ParcatsViewModel} parcatsViewModel
* The parent trace's view model
*/
/**
* @typedef {Object} CategoryBandViewModel
* Object containing calculated category band information. A category band is a region inside a category covering
* paths of a single color
*
* @property key
* Unique key for this model
* @property color
* Band color
* @property rawColor
* Raw color value for band
* @property {Number} width
* Band width
* @property {Number} height
* Band height
* @property {Number} y
* Y position of top of the band with respect to the category
* @property {Number} count
* The number of samples represented by the band
* @property {CategoryViewModel} categoryViewModel
* The parent categorie's view model
* @property {ParcatsViewModel} parcatsViewModel
* The parent trace's view model
*/
/**
* @typedef {Object} PathViewModel
* Object containing calculated parcats path view information
*
* These are quantities that require Layout information to calculate
* @property key
* Unique key for this model
* @property {PathModel} model
* Source path model
* @property {Number} height
* Height of this path (pixels)
* @property {Array.} leftXs
* The x position of the left edge of each display dimension
* @property {Array.} topYs
* The y position of the top of the path for each display dimension
* @property {Array.} dimWidths
* The width of each display dimension
* @property {String} svgD
* SVG path "d" attribute string
* @property {ParcatsViewModel} parcatsViewModel
* The parent trace's view model
*/
},{"../../components/drawing":388,"../../components/fx":406,"../../lib":503,"../../lib/svg_text_utils":529,"../../plot_api/plot_api":540,"@plotly/d3":58,"d3-interpolate":116,"tinycolor2":312}],886:[function(_dereq_,module,exports){
'use strict';
var parcats = _dereq_('./parcats');
/**
* Create / update parcat traces
*
* @param {Object} graphDiv
* @param {Array.} parcatsModels
*/
module.exports = function plot(graphDiv, parcatsModels, transitionOpts, makeOnCompleteCallback) {
var fullLayout = graphDiv._fullLayout;
var svg = fullLayout._paper;
var size = fullLayout._size;
parcats(
graphDiv,
svg,
parcatsModels,
{
width: size.w,
height: size.h,
margin: {
t: size.t,
r: size.r,
b: size.b,
l: size.l
}
},
transitionOpts,
makeOnCompleteCallback
);
};
},{"./parcats":885}],887:[function(_dereq_,module,exports){
'use strict';
var colorScaleAttrs = _dereq_('../../components/colorscale/attributes');
var axesAttrs = _dereq_('../../plots/cartesian/layout_attributes');
var fontAttrs = _dereq_('../../plots/font_attributes');
var domainAttrs = _dereq_('../../plots/domain').attributes;
var extendFlat = _dereq_('../../lib/extend').extendFlat;
var templatedArray = _dereq_('../../plot_api/plot_template').templatedArray;
module.exports = {
domain: domainAttrs({name: 'parcoords', trace: true, editType: 'plot'}),
labelangle: {
valType: 'angle',
dflt: 0,
editType: 'plot',
},
labelside: {
valType: 'enumerated',
values: ['top', 'bottom'],
dflt: 'top',
editType: 'plot',
},
labelfont: fontAttrs({
editType: 'plot',
}),
tickfont: fontAttrs({
editType: 'plot',
}),
rangefont: fontAttrs({
editType: 'plot',
}),
dimensions: templatedArray('dimension', {
label: {
valType: 'string',
editType: 'plot',
},
// TODO: better way to determine ordinal vs continuous axes,
// so users can use tickvals/ticktext with a continuous axis.
tickvals: extendFlat({}, axesAttrs.tickvals, {
editType: 'plot',
}),
ticktext: extendFlat({}, axesAttrs.ticktext, {
editType: 'plot',
}),
tickformat: extendFlat({}, axesAttrs.tickformat, {
editType: 'plot'
}),
visible: {
valType: 'boolean',
dflt: true,
editType: 'plot',
},
range: {
valType: 'info_array',
items: [
{valType: 'number', editType: 'plot'},
{valType: 'number', editType: 'plot'}
],
editType: 'plot',
},
constraintrange: {
valType: 'info_array',
freeLength: true,
dimensions: '1-2',
items: [
{valType: 'any', editType: 'plot'},
{valType: 'any', editType: 'plot'}
],
editType: 'plot',
},
multiselect: {
valType: 'boolean',
dflt: true,
editType: 'plot',
},
values: {
valType: 'data_array',
editType: 'calc',
},
editType: 'calc',
}),
line: extendFlat({editType: 'calc'},
colorScaleAttrs('line', {
// the default autocolorscale isn't quite usable for parcoords due to context ambiguity around 0 (grey, off-white)
// autocolorscale therefore defaults to false too, to avoid being overridden by the blue-white-red autocolor palette
colorscaleDflt: 'Viridis',
autoColorDflt: false,
editTypeOverride: 'calc'
})
)
};
},{"../../components/colorscale/attributes":373,"../../lib/extend":493,"../../plot_api/plot_template":543,"../../plots/cartesian/layout_attributes":569,"../../plots/domain":584,"../../plots/font_attributes":585}],888:[function(_dereq_,module,exports){
'use strict';
var c = _dereq_('./constants');
var d3 = _dereq_('@plotly/d3');
var keyFun = _dereq_('../../lib/gup').keyFun;
var repeat = _dereq_('../../lib/gup').repeat;
var sortAsc = _dereq_('../../lib').sorterAsc;
var strTranslate = _dereq_('../../lib').strTranslate;
var snapRatio = c.bar.snapRatio;
function snapOvershoot(v, vAdjacent) { return v * (1 - snapRatio) + vAdjacent * snapRatio; }
var snapClose = c.bar.snapClose;
function closeToCovering(v, vAdjacent) { return v * (1 - snapClose) + vAdjacent * snapClose; }
// snap for the low end of a range on an ordinal scale
// on an ordinal scale, always show some overshoot from the exact value,
// so it's clear we're covering it
// find the interval we're in, and snap to 1/4 the distance to the next
// these two could be unified at a slight loss of readability / perf
function ordinalScaleSnap(isHigh, a, v, existingRanges) {
if(overlappingExisting(v, existingRanges)) return v;
var dir = isHigh ? -1 : 1;
var first = 0;
var last = a.length - 1;
if(dir < 0) {
var tmp = first;
first = last;
last = tmp;
}
var aHere = a[first];
var aPrev = aHere;
for(var i = first; dir * i < dir * last; i += dir) {
var nextI = i + dir;
var aNext = a[nextI];
// very close to the previous - snap down to it
if(dir * v < dir * closeToCovering(aHere, aNext)) return snapOvershoot(aHere, aPrev);
if(dir * v < dir * aNext || nextI === last) return snapOvershoot(aNext, aHere);
aPrev = aHere;
aHere = aNext;
}
}
function overlappingExisting(v, existingRanges) {
for(var i = 0; i < existingRanges.length; i++) {
if(v >= existingRanges[i][0] && v <= existingRanges[i][1]) return true;
}
return false;
}
function barHorizontalSetup(selection) {
selection
.attr('x', -c.bar.captureWidth / 2)
.attr('width', c.bar.captureWidth);
}
function backgroundBarHorizontalSetup(selection) {
selection
.attr('visibility', 'visible')
.style('visibility', 'visible')
.attr('fill', 'yellow')
.attr('opacity', 0);
}
function setHighlight(d) {
if(!d.brush.filterSpecified) {
return '0,' + d.height;
}
var pixelRanges = unitToPx(d.brush.filter.getConsolidated(), d.height);
var dashArray = [0]; // we start with a 0 length selection as filter ranges are inclusive, not exclusive
var p, sectionHeight, iNext;
var currentGap = pixelRanges.length ? pixelRanges[0][0] : null;
for(var i = 0; i < pixelRanges.length; i++) {
p = pixelRanges[i];
sectionHeight = p[1] - p[0];
dashArray.push(currentGap);
dashArray.push(sectionHeight);
iNext = i + 1;
if(iNext < pixelRanges.length) {
currentGap = pixelRanges[iNext][0] - p[1];
}
}
dashArray.push(d.height);
// d.height is added at the end to ensure that (1) we have an even number of dasharray points, MDN page says
// "If an odd number of values is provided, then the list of values is repeated to yield an even number of values."
// and (2) it's _at least_ as long as the full height (even if range is minuscule and at the bottom) though this
// may not be necessary, maybe duplicating the last point would do too. But no harm in a longer dasharray than line.
return dashArray;
}
function unitToPx(unitRanges, height) {
return unitRanges.map(function(pr) {
return pr.map(function(v) { return Math.max(0, v * height); }).sort(sortAsc);
});
}
// is the cursor over the north, middle, or south of a bar?
// the end handles extend over the last 10% of the bar
function getRegion(fPix, y) {
var pad = c.bar.handleHeight;
if(y > fPix[1] + pad || y < fPix[0] - pad) return;
if(y >= 0.9 * fPix[1] + 0.1 * fPix[0]) return 'n';
if(y <= 0.9 * fPix[0] + 0.1 * fPix[1]) return 's';
return 'ns';
}
function clearCursor() {
d3.select(document.body)
.style('cursor', null);
}
function styleHighlight(selection) {
// stroke-dasharray is used to minimize the number of created DOM nodes, because the requirement calls for up to
// 1000 individual selections on an axis, and there can be 60 axes per parcoords, and multiple parcoords per
// dashboard. The technique is similar to https://codepen.io/monfera/pen/rLYqWR and using a `polyline` with
// multiple sections, or a `path` element via its `d` attribute would also be DOM-sparing alternatives.
selection.attr('stroke-dasharray', setHighlight);
}
function renderHighlight(root, tweenCallback) {
var bar = d3.select(root).selectAll('.highlight, .highlight-shadow');
var barToStyle = tweenCallback ? bar.transition().duration(c.bar.snapDuration).each('end', tweenCallback) : bar;
styleHighlight(barToStyle);
}
function getInterval(d, y) {
var b = d.brush;
var active = b.filterSpecified;
var closestInterval = NaN;
var out = {};
var i;
if(active) {
var height = d.height;
var intervals = b.filter.getConsolidated();
var pixIntervals = unitToPx(intervals, height);
var hoveredInterval = NaN;
var previousInterval = NaN;
var nextInterval = NaN;
for(i = 0; i <= pixIntervals.length; i++) {
var p = pixIntervals[i];
if(p && p[0] <= y && y <= p[1]) {
// over a bar
hoveredInterval = i;
break;
} else {
// between bars, or before/after the first/last bar
previousInterval = i ? i - 1 : NaN;
if(p && p[0] > y) {
nextInterval = i;
break; // no point continuing as intervals are non-overlapping and sorted; could use log search
}
}
}
closestInterval = hoveredInterval;
if(isNaN(closestInterval)) {
if(isNaN(previousInterval) || isNaN(nextInterval)) {
closestInterval = isNaN(previousInterval) ? nextInterval : previousInterval;
} else {
closestInterval = (y - pixIntervals[previousInterval][1] < pixIntervals[nextInterval][0] - y) ?
previousInterval : nextInterval;
}
}
if(!isNaN(closestInterval)) {
var fPix = pixIntervals[closestInterval];
var region = getRegion(fPix, y);
if(region) {
out.interval = intervals[closestInterval];
out.intervalPix = fPix;
out.region = region;
}
}
}
if(d.ordinal && !out.region) {
var a = d.unitTickvals;
var unitLocation = d.unitToPaddedPx.invert(y);
for(i = 0; i < a.length; i++) {
var rangei = [
a[Math.max(i - 1, 0)] * 0.25 + a[i] * 0.75,
a[Math.min(i + 1, a.length - 1)] * 0.25 + a[i] * 0.75
];
if(unitLocation >= rangei[0] && unitLocation <= rangei[1]) {
out.clickableOrdinalRange = rangei;
break;
}
}
}
return out;
}
function dragstart(lThis, d) {
d3.event.sourceEvent.stopPropagation();
var y = d.height - d3.mouse(lThis)[1] - 2 * c.verticalPadding;
var unitLocation = d.unitToPaddedPx.invert(y);
var b = d.brush;
var interval = getInterval(d, y);
var unitRange = interval.interval;
var s = b.svgBrush;
s.wasDragged = false; // we start assuming there won't be a drag - useful for reset
s.grabbingBar = interval.region === 'ns';
if(s.grabbingBar) {
var pixelRange = unitRange.map(d.unitToPaddedPx);
s.grabPoint = y - pixelRange[0] - c.verticalPadding;
s.barLength = pixelRange[1] - pixelRange[0];
}
s.clickableOrdinalRange = interval.clickableOrdinalRange;
s.stayingIntervals = (d.multiselect && b.filterSpecified) ? b.filter.getConsolidated() : [];
if(unitRange) {
s.stayingIntervals = s.stayingIntervals.filter(function(int2) {
return int2[0] !== unitRange[0] && int2[1] !== unitRange[1];
});
}
s.startExtent = interval.region ? unitRange[interval.region === 's' ? 1 : 0] : unitLocation;
d.parent.inBrushDrag = true;
s.brushStartCallback();
}
function drag(lThis, d) {
d3.event.sourceEvent.stopPropagation();
var y = d.height - d3.mouse(lThis)[1] - 2 * c.verticalPadding;
var s = d.brush.svgBrush;
s.wasDragged = true;
s._dragging = true;
if(s.grabbingBar) { // moving the bar
s.newExtent = [y - s.grabPoint, y + s.barLength - s.grabPoint].map(d.unitToPaddedPx.invert);
} else { // south/north drag or new bar creation
s.newExtent = [s.startExtent, d.unitToPaddedPx.invert(y)].sort(sortAsc);
}
d.brush.filterSpecified = true;
s.extent = s.stayingIntervals.concat([s.newExtent]);
s.brushCallback(d);
renderHighlight(lThis.parentNode);
}
function dragend(lThis, d) {
var brush = d.brush;
var filter = brush.filter;
var s = brush.svgBrush;
if(!s._dragging) { // i.e. click
// mock zero drag
mousemove(lThis, d);
drag(lThis, d);
// remember it is a click not a drag
d.brush.svgBrush.wasDragged = false;
}
s._dragging = false;
var e = d3.event;
e.sourceEvent.stopPropagation();
var grabbingBar = s.grabbingBar;
s.grabbingBar = false;
s.grabLocation = undefined;
d.parent.inBrushDrag = false;
clearCursor(); // instead of clearing, a nicer thing would be to set it according to current location
if(!s.wasDragged) { // a click+release on the same spot (ie. w/o dragging) means a bar or full reset
s.wasDragged = undefined; // logic-wise unneeded, just shows `wasDragged` has no longer a meaning
if(s.clickableOrdinalRange) {
if(brush.filterSpecified && d.multiselect) {
s.extent.push(s.clickableOrdinalRange);
} else {
s.extent = [s.clickableOrdinalRange];
brush.filterSpecified = true;
}
} else if(grabbingBar) {
s.extent = s.stayingIntervals;
if(s.extent.length === 0) {
brushClear(brush);
}
} else {
brushClear(brush);
}
s.brushCallback(d);
renderHighlight(lThis.parentNode);
s.brushEndCallback(brush.filterSpecified ? filter.getConsolidated() : []);
return; // no need to fuse intervals or snap to ordinals, so we can bail early
}
var mergeIntervals = function() {
// Key piece of logic: once the button is released, possibly overlapping intervals will be fused:
// Here it's done immediately on click release while on ordinal snap transition it's done at the end
filter.set(filter.getConsolidated());
};
if(d.ordinal) {
var a = d.unitTickvals;
if(a[a.length - 1] < a[0]) a.reverse();
s.newExtent = [
ordinalScaleSnap(0, a, s.newExtent[0], s.stayingIntervals),
ordinalScaleSnap(1, a, s.newExtent[1], s.stayingIntervals)
];
var hasNewExtent = s.newExtent[1] > s.newExtent[0];
s.extent = s.stayingIntervals.concat(hasNewExtent ? [s.newExtent] : []);
if(!s.extent.length) {
brushClear(brush);
}
s.brushCallback(d);
if(hasNewExtent) {
// merging intervals post the snap tween
renderHighlight(lThis.parentNode, mergeIntervals);
} else {
// if no new interval, don't animate, just redraw the highlight immediately
mergeIntervals();
renderHighlight(lThis.parentNode);
}
} else {
mergeIntervals(); // merging intervals immediately
}
s.brushEndCallback(brush.filterSpecified ? filter.getConsolidated() : []);
}
function mousemove(lThis, d) {
var y = d.height - d3.mouse(lThis)[1] - 2 * c.verticalPadding;
var interval = getInterval(d, y);
var cursor = 'crosshair';
if(interval.clickableOrdinalRange) cursor = 'pointer';
else if(interval.region) cursor = interval.region + '-resize';
d3.select(document.body)
.style('cursor', cursor);
}
function attachDragBehavior(selection) {
// There's some fiddling with pointer cursor styling so that the cursor preserves its shape while dragging a brush
// even if the cursor strays from the interacting bar, which is bound to happen as bars are thin and the user
// will inevitably leave the hotspot strip. In this regard, it does something similar to what the D3 brush would do.
selection
.on('mousemove', function(d) {
d3.event.preventDefault();
if(!d.parent.inBrushDrag) mousemove(this, d);
})
.on('mouseleave', function(d) {
if(!d.parent.inBrushDrag) clearCursor();
})
.call(d3.behavior.drag()
.on('dragstart', function(d) { dragstart(this, d); })
.on('drag', function(d) { drag(this, d); })
.on('dragend', function(d) { dragend(this, d); })
);
}
function startAsc(a, b) { return a[0] - b[0]; }
function renderAxisBrush(axisBrush, paperColor) {
var background = axisBrush.selectAll('.background').data(repeat);
background.enter()
.append('rect')
.classed('background', true)
.call(barHorizontalSetup)
.call(backgroundBarHorizontalSetup)
.style('pointer-events', 'auto') // parent pointer events are disabled; we must have it to register events
.attr('transform', strTranslate(0, c.verticalPadding));
background
.call(attachDragBehavior)
.attr('height', function(d) {
return d.height - c.verticalPadding;
});
var highlightShadow = axisBrush.selectAll('.highlight-shadow').data(repeat); // we have a set here, can't call it `extent`
highlightShadow.enter()
.append('line')
.classed('highlight-shadow', true)
.attr('x', -c.bar.width / 2)
.attr('stroke-width', c.bar.width + c.bar.strokeWidth)
.attr('stroke', paperColor)
.attr('opacity', c.bar.strokeOpacity)
.attr('stroke-linecap', 'butt');
highlightShadow
.attr('y1', function(d) { return d.height; })
.call(styleHighlight);
var highlight = axisBrush.selectAll('.highlight').data(repeat); // we have a set here, can't call it `extent`
highlight.enter()
.append('line')
.classed('highlight', true)
.attr('x', -c.bar.width / 2)
.attr('stroke-width', c.bar.width - c.bar.strokeWidth)
.attr('stroke', c.bar.fillColor)
.attr('opacity', c.bar.fillOpacity)
.attr('stroke-linecap', 'butt');
highlight
.attr('y1', function(d) { return d.height; })
.call(styleHighlight);
}
function ensureAxisBrush(axisOverlays, paperColor) {
var axisBrush = axisOverlays.selectAll('.' + c.cn.axisBrush)
.data(repeat, keyFun);
axisBrush.enter()
.append('g')
.classed(c.cn.axisBrush, true);
renderAxisBrush(axisBrush, paperColor);
}
function getBrushExtent(brush) {
return brush.svgBrush.extent.map(function(e) {return e.slice();});
}
function brushClear(brush) {
brush.filterSpecified = false;
brush.svgBrush.extent = [[-Infinity, Infinity]];
}
function axisBrushMoved(callback) {
return function axisBrushMoved(dimension) {
var brush = dimension.brush;
var extent = getBrushExtent(brush);
var newExtent = extent.slice();
brush.filter.set(newExtent);
callback();
};
}
function dedupeRealRanges(intervals) {
// Fuses elements of intervals if they overlap, yielding discontiguous intervals, results.length <= intervals.length
// Currently uses closed intervals, ie. dedupeRealRanges([[400, 800], [300, 400]]) -> [300, 800]
var queue = intervals.slice();
var result = [];
var currentInterval;
var current = queue.shift();
while(current) { // [].shift === undefined, so we don't descend into an empty array
currentInterval = current.slice();
while((current = queue.shift()) && current[0] <= /* right-open interval would need `<` */ currentInterval[1]) {
currentInterval[1] = Math.max(currentInterval[1], current[1]);
}
result.push(currentInterval);
}
if(
result.length === 1 &&
result[0][0] > result[0][1]
) {
// discard result
result = [];
}
return result;
}
function makeFilter() {
var filter = [];
var consolidated;
var bounds;
return {
set: function(a) {
filter = a
.map(function(d) { return d.slice().sort(sortAsc); })
.sort(startAsc);
// handle unselected case
if(filter.length === 1 &&
filter[0][0] === -Infinity &&
filter[0][1] === Infinity) {
filter = [[0, -1]];
}
consolidated = dedupeRealRanges(filter);
bounds = filter.reduce(function(p, n) {
return [Math.min(p[0], n[0]), Math.max(p[1], n[1])];
}, [Infinity, -Infinity]);
},
get: function() { return filter.slice(); },
getConsolidated: function() { return consolidated; },
getBounds: function() { return bounds; }
};
}
function makeBrush(state, rangeSpecified, initialRange, brushStartCallback, brushCallback, brushEndCallback) {
var filter = makeFilter();
filter.set(initialRange);
return {
filter: filter,
filterSpecified: rangeSpecified, // there's a difference between not filtering and filtering a non-proper subset
svgBrush: {
extent: [], // this is where the svgBrush writes contents into
brushStartCallback: brushStartCallback,
brushCallback: axisBrushMoved(brushCallback),
brushEndCallback: brushEndCallback
}
};
}
// for use by supplyDefaults, but it needed tons of pieces from here so
// seemed to make more sense just to put the whole routine here
function cleanRanges(ranges, dimension) {
if(Array.isArray(ranges[0])) {
ranges = ranges.map(function(ri) { return ri.sort(sortAsc); });
if(!dimension.multiselect) ranges = [ranges[0]];
else ranges = dedupeRealRanges(ranges.sort(startAsc));
} else ranges = [ranges.sort(sortAsc)];
// ordinal snapping
if(dimension.tickvals) {
var sortedTickVals = dimension.tickvals.slice().sort(sortAsc);
ranges = ranges.map(function(ri) {
var rSnapped = [
ordinalScaleSnap(0, sortedTickVals, ri[0], []),
ordinalScaleSnap(1, sortedTickVals, ri[1], [])
];
if(rSnapped[1] > rSnapped[0]) return rSnapped;
})
.filter(function(ri) { return ri; });
if(!ranges.length) return;
}
return ranges.length > 1 ? ranges : ranges[0];
}
module.exports = {
makeBrush: makeBrush,
ensureAxisBrush: ensureAxisBrush,
cleanRanges: cleanRanges
};
},{"../../lib":503,"../../lib/gup":500,"./constants":891,"@plotly/d3":58}],889:[function(_dereq_,module,exports){
'use strict';
var d3 = _dereq_('@plotly/d3');
var getModuleCalcData = _dereq_('../../plots/get_data').getModuleCalcData;
var parcoordsPlot = _dereq_('./plot');
var xmlnsNamespaces = _dereq_('../../constants/xmlns_namespaces');
exports.name = 'parcoords';
exports.plot = function(gd) {
var calcData = getModuleCalcData(gd.calcdata, 'parcoords')[0];
if(calcData.length) parcoordsPlot(gd, calcData);
};
exports.clean = function(newFullData, newFullLayout, oldFullData, oldFullLayout) {
var hadParcoords = (oldFullLayout._has && oldFullLayout._has('parcoords'));
var hasParcoords = (newFullLayout._has && newFullLayout._has('parcoords'));
if(hadParcoords && !hasParcoords) {
oldFullLayout._paperdiv.selectAll('.parcoords').remove();
oldFullLayout._glimages.selectAll('*').remove();
}
};
exports.toSVG = function(gd) {
var imageRoot = gd._fullLayout._glimages;
var root = d3.select(gd).selectAll('.svg-container');
var canvases = root.filter(function(d, i) {return i === root.size() - 1;})
.selectAll('.gl-canvas-context, .gl-canvas-focus');
function canvasToImage() {
var canvas = this;
var imageData = canvas.toDataURL('image/png');
var image = imageRoot.append('svg:image');
image.attr({
xmlns: xmlnsNamespaces.svg,
'xlink:href': imageData,
preserveAspectRatio: 'none',
x: 0,
y: 0,
width: canvas.style.width,
height: canvas.style.height
});
}
canvases.each(canvasToImage);
// Chrome / Safari bug workaround - browser apparently loses connection to the defined pattern
// Without the workaround, these browsers 'lose' the filter brush styling (color etc.) after a snapshot
// on a subsequent interaction.
// Firefox works fine without this workaround
window.setTimeout(function() {
d3.selectAll('#filterBarPattern')
.attr('id', 'filterBarPattern');
}, 60);
};
},{"../../constants/xmlns_namespaces":480,"../../plots/get_data":593,"./plot":898,"@plotly/d3":58}],890:[function(_dereq_,module,exports){
'use strict';
var isArrayOrTypedArray = _dereq_('../../lib').isArrayOrTypedArray;
var Colorscale = _dereq_('../../components/colorscale');
var wrap = _dereq_('../../lib/gup').wrap;
module.exports = function calc(gd, trace) {
var lineColor;
var cscale;
if(Colorscale.hasColorscale(trace, 'line') && isArrayOrTypedArray(trace.line.color)) {
lineColor = trace.line.color;
cscale = Colorscale.extractOpts(trace.line).colorscale;
Colorscale.calc(gd, trace, {
vals: lineColor,
containerStr: 'line',
cLetter: 'c'
});
} else {
lineColor = constHalf(trace._length);
cscale = [[0, trace.line.color], [1, trace.line.color]];
}
return wrap({lineColor: lineColor, cscale: cscale});
};
function constHalf(len) {
var out = new Array(len);
for(var i = 0; i < len; i++) {
out[i] = 0.5;
}
return out;
}
},{"../../components/colorscale":378,"../../lib":503,"../../lib/gup":500}],891:[function(_dereq_,module,exports){
'use strict';
module.exports = {
maxDimensionCount: 60, // this cannot be increased without WebGL code refactoring
overdrag: 45,
verticalPadding: 2, // otherwise, horizontal lines on top or bottom are of lower width
tickDistance: 50,
canvasPixelRatio: 1,
blockLineCount: 5000,
layers: ['contextLineLayer', 'focusLineLayer', 'pickLineLayer'],
axisTitleOffset: 28,
axisExtentOffset: 10,
deselectedLineColor: '#777',
bar: {
width: 4, // Visible width of the filter bar
captureWidth: 10, // Mouse-sensitive width for interaction (Fitts law)
fillColor: 'magenta', // Color of the filter bar fill
fillOpacity: 1, // Filter bar fill opacity
snapDuration: 150, // tween duration in ms for brush snap for ordinal axes
snapRatio: 0.25, // ratio of bar extension relative to the distance between two adjacent ordinal values
snapClose: 0.01, // fraction of inter-value distance to snap to the closer one, even if you're not over it
strokeOpacity: 1, // Filter bar side stroke opacity
strokeWidth: 1, // Filter bar side stroke width in pixels
handleHeight: 8, // Height of the filter bar vertical resize areas on top and bottom
handleOpacity: 1, // Opacity of the filter bar vertical resize areas on top and bottom
handleOverlap: 0 // A larger than 0 value causes overlaps with the filter bar, represented as pixels
},
cn: {
axisExtentText: 'axis-extent-text',
parcoordsLineLayers: 'parcoords-line-layers',
parcoordsLineLayer: 'parcoords-lines',
parcoords: 'parcoords',
parcoordsControlView: 'parcoords-control-view',
yAxis: 'y-axis',
axisOverlays: 'axis-overlays',
axis: 'axis',
axisHeading: 'axis-heading',
axisTitle: 'axis-title',
axisExtent: 'axis-extent',
axisExtentTop: 'axis-extent-top',
axisExtentTopText: 'axis-extent-top-text',
axisExtentBottom: 'axis-extent-bottom',
axisExtentBottomText: 'axis-extent-bottom-text',
axisBrush: 'axis-brush'
},
id: {
filterBarPattern: 'filter-bar-pattern'
}
};
},{}],892:[function(_dereq_,module,exports){
'use strict';
var Lib = _dereq_('../../lib');
var hasColorscale = _dereq_('../../components/colorscale/helpers').hasColorscale;
var colorscaleDefaults = _dereq_('../../components/colorscale/defaults');
var handleDomainDefaults = _dereq_('../../plots/domain').defaults;
var handleArrayContainerDefaults = _dereq_('../../plots/array_container_defaults');
var Axes = _dereq_('../../plots/cartesian/axes');
var attributes = _dereq_('./attributes');
var axisBrush = _dereq_('./axisbrush');
var maxDimensionCount = _dereq_('./constants').maxDimensionCount;
var mergeLength = _dereq_('./merge_length');
function handleLineDefaults(traceIn, traceOut, defaultColor, layout, coerce) {
var lineColor = coerce('line.color', defaultColor);
if(hasColorscale(traceIn, 'line') && Lib.isArrayOrTypedArray(lineColor)) {
if(lineColor.length) {
coerce('line.colorscale');
colorscaleDefaults(traceIn, traceOut, layout, coerce, {prefix: 'line.', cLetter: 'c'});
// TODO: I think it would be better to keep showing lines beyond the last line color
// but I'm not sure what color to give these lines - probably black or white
// depending on the background color?
return lineColor.length;
} else {
traceOut.line.color = defaultColor;
}
}
return Infinity;
}
function dimensionDefaults(dimensionIn, dimensionOut, parentOut, opts) {
function coerce(attr, dflt) {
return Lib.coerce(dimensionIn, dimensionOut, attributes.dimensions, attr, dflt);
}
var values = coerce('values');
var visible = coerce('visible');
if(!(values && values.length)) {
visible = dimensionOut.visible = false;
}
if(visible) {
coerce('label');
coerce('tickvals');
coerce('ticktext');
coerce('tickformat');
var range = coerce('range');
dimensionOut._ax = {
_id: 'y',
type: 'linear',
showexponent: 'all',
exponentformat: 'B',
range: range
};
Axes.setConvert(dimensionOut._ax, opts.layout);
coerce('multiselect');
var constraintRange = coerce('constraintrange');
if(constraintRange) {
dimensionOut.constraintrange = axisBrush.cleanRanges(constraintRange, dimensionOut);
}
}
}
module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout) {
function coerce(attr, dflt) {
return Lib.coerce(traceIn, traceOut, attributes, attr, dflt);
}
var dimensionsIn = traceIn.dimensions;
if(Array.isArray(dimensionsIn) && dimensionsIn.length > maxDimensionCount) {
Lib.log('parcoords traces support up to ' + maxDimensionCount + ' dimensions at the moment');
dimensionsIn.splice(maxDimensionCount);
}
var dimensions = handleArrayContainerDefaults(traceIn, traceOut, {
name: 'dimensions',
layout: layout,
handleItemDefaults: dimensionDefaults
});
var len = handleLineDefaults(traceIn, traceOut, defaultColor, layout, coerce);
handleDomainDefaults(traceOut, layout, coerce);
if(!Array.isArray(dimensions) || !dimensions.length) {
traceOut.visible = false;
}
mergeLength(traceOut, dimensions, 'values', len);
// make default font size 10px (default is 12),
// scale linearly with global font size
var fontDflt = {
family: layout.font.family,
size: Math.round(layout.font.size / 1.2),
color: layout.font.color
};
Lib.coerceFont(coerce, 'labelfont', fontDflt);
Lib.coerceFont(coerce, 'tickfont', fontDflt);
Lib.coerceFont(coerce, 'rangefont', fontDflt);
coerce('labelangle');
coerce('labelside');
};
},{"../../components/colorscale/defaults":376,"../../components/colorscale/helpers":377,"../../lib":503,"../../plots/array_container_defaults":549,"../../plots/cartesian/axes":554,"../../plots/domain":584,"./attributes":887,"./axisbrush":888,"./constants":891,"./merge_length":896}],893:[function(_dereq_,module,exports){
'use strict';
var isTypedArray = _dereq_('../../lib').isTypedArray;
exports.convertTypedArray = function(a) {
return isTypedArray(a) ? Array.prototype.slice.call(a) : a;
};
exports.isOrdinal = function(dimension) {
return !!dimension.tickvals;
};
exports.isVisible = function(dimension) {
return dimension.visible || !('visible' in dimension);
};
},{"../../lib":503}],894:[function(_dereq_,module,exports){
'use strict';
module.exports = {
attributes: _dereq_('./attributes'),
supplyDefaults: _dereq_('./defaults'),
calc: _dereq_('./calc'),
plot: _dereq_('./plot'),
colorbar: {
container: 'line',
min: 'cmin',
max: 'cmax'
},
moduleType: 'trace',
name: 'parcoords',
basePlotModule: _dereq_('./base_plot'),
categories: ['gl', 'regl', 'noOpacity', 'noHover'],
meta: {
}
};
},{"./attributes":887,"./base_plot":889,"./calc":890,"./defaults":892,"./plot":898}],895:[function(_dereq_,module,exports){
'use strict';
var glslify = _dereq_('glslify');
var vertexShaderSource = glslify(["precision highp float;\n#define GLSLIFY 1\n\nvarying vec4 fragColor;\n\nattribute vec4 p01_04, p05_08, p09_12, p13_16,\n p17_20, p21_24, p25_28, p29_32,\n p33_36, p37_40, p41_44, p45_48,\n p49_52, p53_56, p57_60, colors;\n\nuniform mat4 dim0A, dim1A, dim0B, dim1B, dim0C, dim1C, dim0D, dim1D,\n loA, hiA, loB, hiB, loC, hiC, loD, hiD;\n\nuniform vec2 resolution, viewBoxPos, viewBoxSize;\nuniform float maskHeight;\nuniform float drwLayer; // 0: context, 1: focus, 2: pick\nuniform vec4 contextColor;\nuniform sampler2D maskTexture, palette;\n\nbool isPick = (drwLayer > 1.5);\nbool isContext = (drwLayer < 0.5);\n\nconst vec4 ZEROS = vec4(0.0, 0.0, 0.0, 0.0);\nconst vec4 UNITS = vec4(1.0, 1.0, 1.0, 1.0);\n\nfloat val(mat4 p, mat4 v) {\n return dot(matrixCompMult(p, v) * UNITS, UNITS);\n}\n\nfloat axisY(float ratio, mat4 A, mat4 B, mat4 C, mat4 D) {\n float y1 = val(A, dim0A) + val(B, dim0B) + val(C, dim0C) + val(D, dim0D);\n float y2 = val(A, dim1A) + val(B, dim1B) + val(C, dim1C) + val(D, dim1D);\n return y1 * (1.0 - ratio) + y2 * ratio;\n}\n\nint iMod(int a, int b) {\n return a - b * (a / b);\n}\n\nbool fOutside(float p, float lo, float hi) {\n return (lo < hi) && (lo > p || p > hi);\n}\n\nbool vOutside(vec4 p, vec4 lo, vec4 hi) {\n return (\n fOutside(p[0], lo[0], hi[0]) ||\n fOutside(p[1], lo[1], hi[1]) ||\n fOutside(p[2], lo[2], hi[2]) ||\n fOutside(p[3], lo[3], hi[3])\n );\n}\n\nbool mOutside(mat4 p, mat4 lo, mat4 hi) {\n return (\n vOutside(p[0], lo[0], hi[0]) ||\n vOutside(p[1], lo[1], hi[1]) ||\n vOutside(p[2], lo[2], hi[2]) ||\n vOutside(p[3], lo[3], hi[3])\n );\n}\n\nbool outsideBoundingBox(mat4 A, mat4 B, mat4 C, mat4 D) {\n return mOutside(A, loA, hiA) ||\n mOutside(B, loB, hiB) ||\n mOutside(C, loC, hiC) ||\n mOutside(D, loD, hiD);\n}\n\nbool outsideRasterMask(mat4 A, mat4 B, mat4 C, mat4 D) {\n mat4 pnts[4];\n pnts[0] = A;\n pnts[1] = B;\n pnts[2] = C;\n pnts[3] = D;\n\n for(int i = 0; i < 4; ++i) {\n for(int j = 0; j < 4; ++j) {\n for(int k = 0; k < 4; ++k) {\n if(0 == iMod(\n int(255.0 * texture2D(maskTexture,\n vec2(\n (float(i * 2 + j / 2) + 0.5) / 8.0,\n (pnts[i][j][k] * (maskHeight - 1.0) + 1.0) / maskHeight\n ))[3]\n ) / int(pow(2.0, float(iMod(j * 4 + k, 8)))),\n 2\n )) return true;\n }\n }\n }\n return false;\n}\n\nvec4 position(bool isContext, float v, mat4 A, mat4 B, mat4 C, mat4 D) {\n float x = 0.5 * sign(v) + 0.5;\n float y = axisY(x, A, B, C, D);\n float z = 1.0 - abs(v);\n\n z += isContext ? 0.0 : 2.0 * float(\n outsideBoundingBox(A, B, C, D) ||\n outsideRasterMask(A, B, C, D)\n );\n\n return vec4(\n 2.0 * (vec2(x, y) * viewBoxSize + viewBoxPos) / resolution - 1.0,\n z,\n 1.0\n );\n}\n\nvoid main() {\n mat4 A = mat4(p01_04, p05_08, p09_12, p13_16);\n mat4 B = mat4(p17_20, p21_24, p25_28, p29_32);\n mat4 C = mat4(p33_36, p37_40, p41_44, p45_48);\n mat4 D = mat4(p49_52, p53_56, p57_60, ZEROS);\n\n float v = colors[3];\n\n gl_Position = position(isContext, v, A, B, C, D);\n\n fragColor =\n isContext ? vec4(contextColor) :\n isPick ? vec4(colors.rgb, 1.0) : texture2D(palette, vec2(abs(v), 0.5));\n}\n"]);
var fragmentShaderSource = glslify(["precision highp float;\n#define GLSLIFY 1\n\nvarying vec4 fragColor;\n\nvoid main() {\n gl_FragColor = fragColor;\n}\n"]);
var maxDim = _dereq_('./constants').maxDimensionCount;
var Lib = _dereq_('../../lib');
// don't change; otherwise near/far plane lines are lost
var depthLimitEpsilon = 1e-6;
// precision of multiselect is the full range divided into this many parts
var maskHeight = 2048;
var dummyPixel = new Uint8Array(4);
var dataPixel = new Uint8Array(4);
var paletteTextureConfig = {
shape: [256, 1],
format: 'rgba',
type: 'uint8',
mag: 'nearest',
min: 'nearest'
};
function ensureDraw(regl) {
regl.read({
x: 0,
y: 0,
width: 1,
height: 1,
data: dummyPixel
});
}
function clear(regl, x, y, width, height) {
var gl = regl._gl;
gl.enable(gl.SCISSOR_TEST);
gl.scissor(x, y, width, height);
regl.clear({color: [0, 0, 0, 0], depth: 1}); // clearing is done in scissored panel only
}
function renderBlock(regl, glAes, renderState, blockLineCount, sampleCount, item) {
var rafKey = item.key;
function render(blockNumber) {
var count = Math.min(blockLineCount, sampleCount - blockNumber * blockLineCount);
if(blockNumber === 0) {
// stop drawing possibly stale glyphs before clearing
window.cancelAnimationFrame(renderState.currentRafs[rafKey]);
delete renderState.currentRafs[rafKey];
clear(regl, item.scissorX, item.scissorY, item.scissorWidth, item.viewBoxSize[1]);
}
if(renderState.clearOnly) {
return;
}
item.count = 2 * count;
item.offset = 2 * blockNumber * blockLineCount;
glAes(item);
if(blockNumber * blockLineCount + count < sampleCount) {
renderState.currentRafs[rafKey] = window.requestAnimationFrame(function() {
render(blockNumber + 1);
});
}
renderState.drawCompleted = false;
}
if(!renderState.drawCompleted) {
ensureDraw(regl);
renderState.drawCompleted = true;
}
// start with rendering item 0; recursion handles the rest
render(0);
}
function adjustDepth(d) {
// WebGL matrix operations use floats with limited precision, potentially causing a number near a border of [0, 1]
// to end up slightly outside the border. With an epsilon, we reduce the chance that a line gets clipped by the
// near or the far plane.
return Math.max(depthLimitEpsilon, Math.min(1 - depthLimitEpsilon, d));
}
function palette(unitToColor, opacity) {
var result = new Array(256);
for(var i = 0; i < 256; i++) {
result[i] = unitToColor(i / 255).concat(opacity);
}
return result;
}
// Maps the sample index [0...sampleCount - 1] to a range of [0, 1] as the shader expects colors in the [0, 1] range.
// but first it shifts the sample index by 0, 8 or 16 bits depending on rgbIndex [0..2]
// with the end result that each line will be of a unique color, making it possible for the pick handler
// to uniquely identify which line is hovered over (bijective mapping).
// The inverse, i.e. readPixel is invoked from 'parcoords.js'
function calcPickColor(i, rgbIndex) {
return (i >>> 8 * rgbIndex) % 256 / 255;
}
function makePoints(sampleCount, dims, color) {
var points = new Array(sampleCount * (maxDim + 4));
var n = 0;
for(var i = 0; i < sampleCount; i++) {
for(var k = 0; k < maxDim; k++) {
points[n++] = (k < dims.length) ? dims[k].paddedUnitValues[i] : 0.5;
}
points[n++] = calcPickColor(i, 2);
points[n++] = calcPickColor(i, 1);
points[n++] = calcPickColor(i, 0);
points[n++] = adjustDepth(color[i]);
}
return points;
}
function makeVecAttr(vecIndex, sampleCount, points) {
var pointPairs = new Array(sampleCount * 8);
var n = 0;
for(var i = 0; i < sampleCount; i++) {
for(var j = 0; j < 2; j++) {
for(var k = 0; k < 4; k++) {
var q = vecIndex * 4 + k;
var v = points[i * 64 + q];
if(q === 63 && j === 0) {
v *= -1;
}
pointPairs[n++] = v;
}
}
}
return pointPairs;
}
function pad2(num) {
var s = '0' + num;
return s.substr(s.length - 2);
}
function getAttrName(i) {
return (i < maxDim) ? 'p' + pad2(i + 1) + '_' + pad2(i + 4) : 'colors';
}
function setAttributes(attributes, sampleCount, points) {
for(var i = 0; i <= maxDim; i += 4) {
attributes[getAttrName(i)](makeVecAttr(i / 4, sampleCount, points));
}
}
function emptyAttributes(regl) {
var attributes = {};
for(var i = 0; i <= maxDim; i += 4) {
attributes[getAttrName(i)] = regl.buffer({usage: 'dynamic', type: 'float', data: new Uint8Array(0)});
}
return attributes;
}
function makeItem(
model, leftmost, rightmost, itemNumber, i0, i1, x, y, panelSizeX, panelSizeY,
crossfilterDimensionIndex, drwLayer, constraints, plotGlPixelRatio
) {
var dims = [[], []];
for(var k = 0; k < 64; k++) {
dims[0][k] = (k === i0) ? 1 : 0;
dims[1][k] = (k === i1) ? 1 : 0;
}
x *= plotGlPixelRatio;
y *= plotGlPixelRatio;
panelSizeX *= plotGlPixelRatio;
panelSizeY *= plotGlPixelRatio;
var overdrag = model.lines.canvasOverdrag * plotGlPixelRatio;
var domain = model.domain;
var canvasWidth = model.canvasWidth * plotGlPixelRatio;
var canvasHeight = model.canvasHeight * plotGlPixelRatio;
var padL = model.pad.l * plotGlPixelRatio;
var padB = model.pad.b * plotGlPixelRatio;
var layoutHeight = model.layoutHeight * plotGlPixelRatio;
var layoutWidth = model.layoutWidth * plotGlPixelRatio;
var deselectedLinesColor = model.deselectedLines.color;
var itemModel = Lib.extendFlat({
key: crossfilterDimensionIndex,
resolution: [canvasWidth, canvasHeight],
viewBoxPos: [x + overdrag, y],
viewBoxSize: [panelSizeX, panelSizeY],
i0: i0,
i1: i1,
dim0A: dims[0].slice(0, 16),
dim0B: dims[0].slice(16, 32),
dim0C: dims[0].slice(32, 48),
dim0D: dims[0].slice(48, 64),
dim1A: dims[1].slice(0, 16),
dim1B: dims[1].slice(16, 32),
dim1C: dims[1].slice(32, 48),
dim1D: dims[1].slice(48, 64),
drwLayer: drwLayer,
contextColor: [
deselectedLinesColor[0] / 255,
deselectedLinesColor[1] / 255,
deselectedLinesColor[2] / 255,
deselectedLinesColor[3] < 1 ?
deselectedLinesColor[3] :
Math.max(1 / 255, Math.pow(1 / model.lines.color.length, 1 / 3))
],
scissorX: (itemNumber === leftmost ? 0 : x + overdrag) + (padL - overdrag) + layoutWidth * domain.x[0],
scissorWidth: (itemNumber === rightmost ? canvasWidth - x + overdrag : panelSizeX + 0.5) + (itemNumber === leftmost ? x + overdrag : 0),
scissorY: y + padB + layoutHeight * domain.y[0],
scissorHeight: panelSizeY,
viewportX: padL - overdrag + layoutWidth * domain.x[0],
viewportY: padB + layoutHeight * domain.y[0],
viewportWidth: canvasWidth,
viewportHeight: canvasHeight
}, constraints);
return itemModel;
}
function expandedPixelRange(bounds) {
var dh = maskHeight - 1;
var a = Math.max(0, Math.floor(bounds[0] * dh), 0);
var b = Math.min(dh, Math.ceil(bounds[1] * dh), dh);
return [
Math.min(a, b),
Math.max(a, b)
];
}
module.exports = function(canvasGL, d) {
// context & pick describe which canvas we're talking about - won't change with new data
var isContext = d.context;
var isPick = d.pick;
var regl = d.regl;
var gl = regl._gl;
var supportedLineWidth = gl.getParameter(gl.ALIASED_LINE_WIDTH_RANGE);
// ensure here that plotGlPixelRatio is within supported range; otherwise regl throws error
var plotGlPixelRatio = Math.max(
supportedLineWidth[0],
Math.min(
supportedLineWidth[1],
d.viewModel.plotGlPixelRatio
)
);
var renderState = {
currentRafs: {},
drawCompleted: true,
clearOnly: false
};
// state to be set by update and used later
var model;
var vm;
var initialDims;
var sampleCount;
var attributes = emptyAttributes(regl);
var maskTexture;
var paletteTexture = regl.texture(paletteTextureConfig);
var prevAxisOrder = [];
update(d);
var glAes = regl({
profile: false,
blend: {
enable: isContext,
func: {
srcRGB: 'src alpha',
dstRGB: 'one minus src alpha',
srcAlpha: 1,
dstAlpha: 1 // 'one minus src alpha'
},
equation: {
rgb: 'add',
alpha: 'add'
},
color: [0, 0, 0, 0]
},
depth: {
enable: !isContext,
mask: true,
func: 'less',
range: [0, 1]
},
// for polygons
cull: {
enable: true,
face: 'back'
},
scissor: {
enable: true,
box: {
x: regl.prop('scissorX'),
y: regl.prop('scissorY'),
width: regl.prop('scissorWidth'),
height: regl.prop('scissorHeight')
}
},
viewport: {
x: regl.prop('viewportX'),
y: regl.prop('viewportY'),
width: regl.prop('viewportWidth'),
height: regl.prop('viewportHeight')
},
dither: false,
vert: vertexShaderSource,
frag: fragmentShaderSource,
primitive: 'lines',
lineWidth: plotGlPixelRatio,
attributes: attributes,
uniforms: {
resolution: regl.prop('resolution'),
viewBoxPos: regl.prop('viewBoxPos'),
viewBoxSize: regl.prop('viewBoxSize'),
dim0A: regl.prop('dim0A'),
dim1A: regl.prop('dim1A'),
dim0B: regl.prop('dim0B'),
dim1B: regl.prop('dim1B'),
dim0C: regl.prop('dim0C'),
dim1C: regl.prop('dim1C'),
dim0D: regl.prop('dim0D'),
dim1D: regl.prop('dim1D'),
loA: regl.prop('loA'),
hiA: regl.prop('hiA'),
loB: regl.prop('loB'),
hiB: regl.prop('hiB'),
loC: regl.prop('loC'),
hiC: regl.prop('hiC'),
loD: regl.prop('loD'),
hiD: regl.prop('hiD'),
palette: paletteTexture,
contextColor: regl.prop('contextColor'),
maskTexture: regl.prop('maskTexture'),
drwLayer: regl.prop('drwLayer'),
maskHeight: regl.prop('maskHeight')
},
offset: regl.prop('offset'),
count: regl.prop('count')
});
function update(dNew) {
model = dNew.model;
vm = dNew.viewModel;
initialDims = vm.dimensions.slice();
sampleCount = initialDims[0] ? initialDims[0].values.length : 0;
var lines = model.lines;
var color = isPick ? lines.color.map(function(_, i) {return i / lines.color.length;}) : lines.color;
var points = makePoints(sampleCount, initialDims, color);
setAttributes(attributes, sampleCount, points);
if(!isContext && !isPick) {
paletteTexture = regl.texture(Lib.extendFlat({
data: palette(model.unitToColor, 255)
}, paletteTextureConfig));
}
}
function makeConstraints(isContext) {
var i, j, k;
var limits = [[], []];
for(k = 0; k < 64; k++) {
var p = (!isContext && k < initialDims.length) ?
initialDims[k].brush.filter.getBounds() : [-Infinity, Infinity];
limits[0][k] = p[0];
limits[1][k] = p[1];
}
var len = maskHeight * 8;
var mask = new Array(len);
for(i = 0; i < len; i++) {
mask[i] = 255;
}
if(!isContext) {
for(i = 0; i < initialDims.length; i++) {
var u = i % 8;
var v = (i - u) / 8;
var bitMask = Math.pow(2, u);
var dim = initialDims[i];
var ranges = dim.brush.filter.get();
if(ranges.length < 2) continue; // bail if the bounding box based filter is sufficient
var prevEnd = expandedPixelRange(ranges[0])[1];
for(j = 1; j < ranges.length; j++) {
var nextRange = expandedPixelRange(ranges[j]);
for(k = prevEnd + 1; k < nextRange[0]; k++) {
mask[k * 8 + v] &= ~bitMask;
}
prevEnd = Math.max(prevEnd, nextRange[1]);
}
}
}
var textureData = {
// 8 units x 8 bits = 64 bits, just sufficient for the almost 64 dimensions we support
shape: [8, maskHeight],
format: 'alpha',
type: 'uint8',
mag: 'nearest',
min: 'nearest',
data: mask
};
if(maskTexture) maskTexture(textureData);
else maskTexture = regl.texture(textureData);
return {
maskTexture: maskTexture,
maskHeight: maskHeight,
loA: limits[0].slice(0, 16),
loB: limits[0].slice(16, 32),
loC: limits[0].slice(32, 48),
loD: limits[0].slice(48, 64),
hiA: limits[1].slice(0, 16),
hiB: limits[1].slice(16, 32),
hiC: limits[1].slice(32, 48),
hiD: limits[1].slice(48, 64),
};
}
function renderGLParcoords(panels, setChanged, clearOnly) {
var panelCount = panels.length;
var i;
var leftmost;
var rightmost;
var lowestX = Infinity;
var highestX = -Infinity;
for(i = 0; i < panelCount; i++) {
if(panels[i].dim0.canvasX < lowestX) {
lowestX = panels[i].dim0.canvasX;
leftmost = i;
}
if(panels[i].dim1.canvasX > highestX) {
highestX = panels[i].dim1.canvasX;
rightmost = i;
}
}
if(panelCount === 0) {
// clear canvas here, as the panel iteration below will not enter the loop body
clear(regl, 0, 0, model.canvasWidth, model.canvasHeight);
}
var constraints = makeConstraints(isContext);
for(i = 0; i < panelCount; i++) {
var p = panels[i];
var i0 = p.dim0.crossfilterDimensionIndex;
var i1 = p.dim1.crossfilterDimensionIndex;
var x = p.canvasX;
var y = p.canvasY;
var nextX = x + p.panelSizeX;
var plotGlPixelRatio = p.plotGlPixelRatio;
if(setChanged ||
!prevAxisOrder[i0] ||
prevAxisOrder[i0][0] !== x ||
prevAxisOrder[i0][1] !== nextX
) {
prevAxisOrder[i0] = [x, nextX];
var item = makeItem(
model,
leftmost, rightmost, i, i0, i1, x, y,
p.panelSizeX, p.panelSizeY,
p.dim0.crossfilterDimensionIndex,
isContext ? 0 : isPick ? 2 : 1,
constraints,
plotGlPixelRatio
);
renderState.clearOnly = clearOnly;
var blockLineCount = setChanged ? model.lines.blockLineCount : sampleCount;
renderBlock(
regl, glAes, renderState, blockLineCount, sampleCount, item
);
}
}
}
function readPixel(canvasX, canvasY) {
regl.read({
x: canvasX,
y: canvasY,
width: 1,
height: 1,
data: dataPixel
});
return dataPixel;
}
function readPixels(canvasX, canvasY, width, height) {
var pixelArray = new Uint8Array(4 * width * height);
regl.read({
x: canvasX,
y: canvasY,
width: width,
height: height,
data: pixelArray
});
return pixelArray;
}
function destroy() {
canvasGL.style['pointer-events'] = 'none';
paletteTexture.destroy();
if(maskTexture) maskTexture.destroy();
for(var k in attributes) attributes[k].destroy();
}
return {
render: renderGLParcoords,
readPixel: readPixel,
readPixels: readPixels,
destroy: destroy,
update: update
};
};
},{"../../lib":503,"./constants":891,"glslify":227}],896:[function(_dereq_,module,exports){
'use strict';
/**
* mergeLength: set trace length as the minimum of all dimension data lengths
* and propagates this length into each dimension
*
* @param {object} traceOut: the fullData trace
* @param {Array(object)} dimensions: array of dimension objects
* @param {string} dataAttr: the attribute of each dimension containing the data
* @param {integer} len: an already-existing length from other attributes
*/
module.exports = function(traceOut, dimensions, dataAttr, len) {
if(!len) len = Infinity;
var i, dimi;
for(i = 0; i < dimensions.length; i++) {
dimi = dimensions[i];
if(dimi.visible) len = Math.min(len, dimi[dataAttr].length);
}
if(len === Infinity) len = 0;
traceOut._length = len;
for(i = 0; i < dimensions.length; i++) {
dimi = dimensions[i];
if(dimi.visible) dimi._length = len;
}
return len;
};
},{}],897:[function(_dereq_,module,exports){
'use strict';
var d3 = _dereq_('@plotly/d3');
var Lib = _dereq_('../../lib');
var numberFormat = Lib.numberFormat;
var rgba = _dereq_('color-rgba');
var Axes = _dereq_('../../plots/cartesian/axes');
var strRotate = Lib.strRotate;
var strTranslate = Lib.strTranslate;
var svgTextUtils = _dereq_('../../lib/svg_text_utils');
var Drawing = _dereq_('../../components/drawing');
var Colorscale = _dereq_('../../components/colorscale');
var gup = _dereq_('../../lib/gup');
var keyFun = gup.keyFun;
var repeat = gup.repeat;
var unwrap = gup.unwrap;
var helpers = _dereq_('./helpers');
var c = _dereq_('./constants');
var brush = _dereq_('./axisbrush');
var lineLayerMaker = _dereq_('./lines');
function findExtreme(fn, values, len) {
return Lib.aggNums(fn, null, values, len);
}
function findExtremes(values, len) {
return fixExtremes(
findExtreme(Math.min, values, len),
findExtreme(Math.max, values, len)
);
}
function dimensionExtent(dimension) {
var range = dimension.range;
return range ?
fixExtremes(range[0], range[1]) :
findExtremes(dimension.values, dimension._length);
}
function fixExtremes(lo, hi) {
if(isNaN(lo) || !isFinite(lo)) {
lo = 0;
}
if(isNaN(hi) || !isFinite(hi)) {
hi = 0;
}
// avoid a degenerate (zero-width) domain
if(lo === hi) {
if(lo === 0) {
// no use to multiplying zero, so add/subtract in this case
lo -= 1;
hi += 1;
} else {
// this keeps the range in the order of magnitude of the data
lo *= 0.9;
hi *= 1.1;
}
}
return [lo, hi];
}
function toText(formatter, texts) {
if(texts) {
return function(v, i) {
var text = texts[i];
if(text === null || text === undefined) return formatter(v);
return text;
};
}
return formatter;
}
function domainScale(height, padding, dimension, tickvals, ticktext) {
var extent = dimensionExtent(dimension);
if(tickvals) {
return d3.scale.ordinal()
.domain(tickvals.map(toText(numberFormat(dimension.tickformat), ticktext)))
.range(tickvals
.map(function(d) {
var unitVal = (d - extent[0]) / (extent[1] - extent[0]);
return (height - padding + unitVal * (2 * padding - height));
})
);
}
return d3.scale.linear()
.domain(extent)
.range([height - padding, padding]);
}
function unitToPaddedPx(height, padding) {
return d3.scale.linear().range([padding, height - padding]);
}
function domainToPaddedUnitScale(dimension, padFraction) {
return d3.scale.linear()
.domain(dimensionExtent(dimension))
.range([padFraction, 1 - padFraction]);
}
function ordinalScale(dimension) {
if(!dimension.tickvals) return;
var extent = dimensionExtent(dimension);
return d3.scale.ordinal()
.domain(dimension.tickvals)
.range(dimension.tickvals.map(function(d) {
return (d - extent[0]) / (extent[1] - extent[0]);
}));
}
function unitToColorScale(cscale) {
var colorStops = cscale.map(function(d) { return d[0]; });
var colorTuples = cscale.map(function(d) {
var RGBA = rgba(d[1]);
return d3.rgb('rgb(' + RGBA[0] + ',' + RGBA[1] + ',' + RGBA[2] + ')');
});
var prop = function(n) { return function(o) { return o[n]; }; };
// We can't use d3 color interpolation as we may have non-uniform color palette raster
// (various color stop distances).
var polylinearUnitScales = 'rgb'.split('').map(function(key) {
return d3.scale.linear()
.clamp(true)
.domain(colorStops)
.range(colorTuples.map(prop(key)));
});
return function(d) {
return polylinearUnitScales.map(function(s) {
return s(d);
});
};
}
function someFiltersActive(view) {
return view.dimensions.some(function(p) {
return p.brush.filterSpecified;
});
}
function model(layout, d, i) {
var cd0 = unwrap(d);
var trace = cd0.trace;
var lineColor = helpers.convertTypedArray(cd0.lineColor);
var line = trace.line;
var deselectedLines = {color: rgba(c.deselectedLineColor)};
var cOpts = Colorscale.extractOpts(line);
var cscale = cOpts.reversescale ? Colorscale.flipScale(cd0.cscale) : cd0.cscale;
var domain = trace.domain;
var dimensions = trace.dimensions;
var width = layout.width;
var labelAngle = trace.labelangle;
var labelSide = trace.labelside;
var labelFont = trace.labelfont;
var tickFont = trace.tickfont;
var rangeFont = trace.rangefont;
var lines = Lib.extendDeepNoArrays({}, line, {
color: lineColor.map(d3.scale.linear().domain(
dimensionExtent({
values: lineColor,
range: [cOpts.min, cOpts.max],
_length: trace._length
})
)),
blockLineCount: c.blockLineCount,
canvasOverdrag: c.overdrag * c.canvasPixelRatio
});
var groupWidth = Math.floor(width * (domain.x[1] - domain.x[0]));
var groupHeight = Math.floor(layout.height * (domain.y[1] - domain.y[0]));
var pad = layout.margin || {l: 80, r: 80, t: 100, b: 80};
var rowContentWidth = groupWidth;
var rowHeight = groupHeight;
return {
key: i,
colCount: dimensions.filter(helpers.isVisible).length,
dimensions: dimensions,
tickDistance: c.tickDistance,
unitToColor: unitToColorScale(cscale),
lines: lines,
deselectedLines: deselectedLines,
labelAngle: labelAngle,
labelSide: labelSide,
labelFont: labelFont,
tickFont: tickFont,
rangeFont: rangeFont,
layoutWidth: width,
layoutHeight: layout.height,
domain: domain,
translateX: domain.x[0] * width,
translateY: layout.height - domain.y[1] * layout.height,
pad: pad,
canvasWidth: rowContentWidth * c.canvasPixelRatio + 2 * lines.canvasOverdrag,
canvasHeight: rowHeight * c.canvasPixelRatio,
width: rowContentWidth,
height: rowHeight,
canvasPixelRatio: c.canvasPixelRatio
};
}
function viewModel(state, callbacks, model) {
var width = model.width;
var height = model.height;
var dimensions = model.dimensions;
var canvasPixelRatio = model.canvasPixelRatio;
var xScale = function(d) {return width * d / Math.max(1, model.colCount - 1);};
var unitPad = c.verticalPadding / height;
var _unitToPaddedPx = unitToPaddedPx(height, c.verticalPadding);
var vm = {
key: model.key,
xScale: xScale,
model: model,
inBrushDrag: false // consider factoring it out and putting it in a centralized global-ish gesture state object
};
var uniqueKeys = {};
vm.dimensions = dimensions.filter(helpers.isVisible).map(function(dimension, i) {
var domainToPaddedUnit = domainToPaddedUnitScale(dimension, unitPad);
var foundKey = uniqueKeys[dimension.label];
uniqueKeys[dimension.label] = (foundKey || 0) + 1;
var key = dimension.label + (foundKey ? '__' + foundKey : '');
var specifiedConstraint = dimension.constraintrange;
var filterRangeSpecified = specifiedConstraint && specifiedConstraint.length;
if(filterRangeSpecified && !Array.isArray(specifiedConstraint[0])) {
specifiedConstraint = [specifiedConstraint];
}
var filterRange = filterRangeSpecified ?
specifiedConstraint.map(function(d) { return d.map(domainToPaddedUnit); }) :
[[-Infinity, Infinity]];
var brushMove = function() {
var p = vm;
p.focusLayer && p.focusLayer.render(p.panels, true);
var filtersActive = someFiltersActive(p);
if(!state.contextShown() && filtersActive) {
p.contextLayer && p.contextLayer.render(p.panels, true);
state.contextShown(true);
} else if(state.contextShown() && !filtersActive) {
p.contextLayer && p.contextLayer.render(p.panels, true, true);
state.contextShown(false);
}
};
var truncatedValues = dimension.values;
if(truncatedValues.length > dimension._length) {
truncatedValues = truncatedValues.slice(0, dimension._length);
}
var tickvals = dimension.tickvals;
var ticktext;
function makeTickItem(v, i) { return {val: v, text: ticktext[i]}; }
function sortTickItem(a, b) { return a.val - b.val; }
if(Array.isArray(tickvals) && tickvals.length) {
ticktext = dimension.ticktext;
// ensure ticktext and tickvals have same length
if(!Array.isArray(ticktext) || !ticktext.length) {
ticktext = tickvals.map(numberFormat(dimension.tickformat));
} else if(ticktext.length > tickvals.length) {
ticktext = ticktext.slice(0, tickvals.length);
} else if(tickvals.length > ticktext.length) {
tickvals = tickvals.slice(0, ticktext.length);
}
// check if we need to sort tickvals/ticktext
for(var j = 1; j < tickvals.length; j++) {
if(tickvals[j] < tickvals[j - 1]) {
var tickItems = tickvals.map(makeTickItem).sort(sortTickItem);
for(var k = 0; k < tickvals.length; k++) {
tickvals[k] = tickItems[k].val;
ticktext[k] = tickItems[k].text;
}
break;
}
}
} else tickvals = undefined;
truncatedValues = helpers.convertTypedArray(truncatedValues);
return {
key: key,
label: dimension.label,
tickFormat: dimension.tickformat,
tickvals: tickvals,
ticktext: ticktext,
ordinal: helpers.isOrdinal(dimension),
multiselect: dimension.multiselect,
xIndex: i,
crossfilterDimensionIndex: i,
visibleIndex: dimension._index,
height: height,
values: truncatedValues,
paddedUnitValues: truncatedValues.map(domainToPaddedUnit),
unitTickvals: tickvals && tickvals.map(domainToPaddedUnit),
xScale: xScale,
x: xScale(i),
canvasX: xScale(i) * canvasPixelRatio,
unitToPaddedPx: _unitToPaddedPx,
domainScale: domainScale(height, c.verticalPadding, dimension, tickvals, ticktext),
ordinalScale: ordinalScale(dimension),
parent: vm,
model: model,
brush: brush.makeBrush(
state,
filterRangeSpecified,
filterRange,
function() {
state.linePickActive(false);
},
brushMove,
function(f) {
vm.focusLayer.render(vm.panels, true);
vm.pickLayer && vm.pickLayer.render(vm.panels, true);
state.linePickActive(true);
if(callbacks && callbacks.filterChanged) {
var invScale = domainToPaddedUnit.invert;
// update gd.data as if a Plotly.restyle were fired
var newRanges = f.map(function(r) {
return r.map(invScale).sort(Lib.sorterAsc);
}).sort(function(a, b) { return a[0] - b[0]; });
callbacks.filterChanged(vm.key, dimension._index, newRanges);
}
}
)
};
});
return vm;
}
function styleExtentTexts(selection) {
selection
.classed(c.cn.axisExtentText, true)
.attr('text-anchor', 'middle')
.style('cursor', 'default');
}
function parcoordsInteractionState() {
var linePickActive = true;
var contextShown = false;
return {
linePickActive: function(val) {return arguments.length ? linePickActive = !!val : linePickActive;},
contextShown: function(val) {return arguments.length ? contextShown = !!val : contextShown;}
};
}
function calcTilt(angle, position) {
var dir = (position === 'top') ? 1 : -1;
var radians = angle * Math.PI / 180;
var dx = Math.sin(radians);
var dy = Math.cos(radians);
return {
dir: dir,
dx: dx,
dy: dy,
degrees: angle
};
}
function updatePanelLayout(yAxis, vm, plotGlPixelRatio) {
var panels = vm.panels || (vm.panels = []);
var data = yAxis.data();
for(var i = 0; i < data.length - 1; i++) {
var p = panels[i] || (panels[i] = {});
var dim0 = data[i];
var dim1 = data[i + 1];
p.dim0 = dim0;
p.dim1 = dim1;
p.canvasX = dim0.canvasX;
p.panelSizeX = dim1.canvasX - dim0.canvasX;
p.panelSizeY = vm.model.canvasHeight;
p.y = 0;
p.canvasY = 0;
p.plotGlPixelRatio = plotGlPixelRatio;
}
}
function calcAllTicks(cd) {
for(var i = 0; i < cd.length; i++) {
for(var j = 0; j < cd[i].length; j++) {
var trace = cd[i][j].trace;
var dimensions = trace.dimensions;
for(var k = 0; k < dimensions.length; k++) {
var values = dimensions[k].values;
var dim = dimensions[k]._ax;
if(dim) {
if(!dim.range) {
dim.range = findExtremes(values, trace._length);
} else {
dim.range = fixExtremes(dim.range[0], dim.range[1]);
}
if(!dim.dtick) {
dim.dtick = 0.01 * (Math.abs(dim.range[1] - dim.range[0]) || 1);
}
dim.tickformat = dimensions[k].tickformat;
Axes.calcTicks(dim);
dim.cleanRange();
}
}
}
}
}
function linearFormat(dim, v) {
return Axes.tickText(dim._ax, v, false).text;
}
function extremeText(d, isTop) {
if(d.ordinal) return '';
var domain = d.domainScale.domain();
var v = (domain[isTop ? domain.length - 1 : 0]);
return linearFormat(d.model.dimensions[d.visibleIndex], v);
}
module.exports = function parcoords(gd, cdModule, layout, callbacks) {
var fullLayout = gd._fullLayout;
var svg = fullLayout._toppaper;
var glContainer = fullLayout._glcontainer;
var plotGlPixelRatio = gd._context.plotGlPixelRatio;
var paperColor = gd._fullLayout.paper_bgcolor;
calcAllTicks(cdModule);
var state = parcoordsInteractionState();
var vm = cdModule
.filter(function(d) { return unwrap(d).trace.visible; })
.map(model.bind(0, layout))
.map(viewModel.bind(0, state, callbacks));
glContainer.each(function(d, i) {
return Lib.extendFlat(d, vm[i]);
});
var glLayers = glContainer.selectAll('.gl-canvas')
.each(function(d) {
// FIXME: figure out how to handle multiple instances
d.viewModel = vm[0];
d.viewModel.plotGlPixelRatio = plotGlPixelRatio;
d.viewModel.paperColor = paperColor;
d.model = d.viewModel ? d.viewModel.model : null;
});
var lastHovered = null;
var pickLayer = glLayers.filter(function(d) {return d.pick;});
// emit hover / unhover event
pickLayer
.style('pointer-events', 'auto')
.on('mousemove', function(d) {
if(state.linePickActive() && d.lineLayer && callbacks && callbacks.hover) {
var event = d3.event;
var cw = this.width;
var ch = this.height;
var pointer = d3.mouse(this);
var x = pointer[0];
var y = pointer[1];
if(x < 0 || y < 0 || x >= cw || y >= ch) {
return;
}
var pixel = d.lineLayer.readPixel(x, ch - 1 - y);
var found = pixel[3] !== 0;
// inverse of the calcPickColor in `lines.js`; detailed comment there
var curveNumber = found ? pixel[2] + 256 * (pixel[1] + 256 * pixel[0]) : null;
var eventData = {
x: x,
y: y,
clientX: event.clientX,
clientY: event.clientY,
dataIndex: d.model.key,
curveNumber: curveNumber
};
if(curveNumber !== lastHovered) { // don't unnecessarily repeat the same hit (or miss)
if(found) {
callbacks.hover(eventData);
} else if(callbacks.unhover) {
callbacks.unhover(eventData);
}
lastHovered = curveNumber;
}
}
});
glLayers
.style('opacity', function(d) {return d.pick ? 0 : 1;});
svg.style('background', 'rgba(255, 255, 255, 0)');
var controlOverlay = svg.selectAll('.' + c.cn.parcoords)
.data(vm, keyFun);
controlOverlay.exit().remove();
controlOverlay.enter()
.append('g')
.classed(c.cn.parcoords, true)
.style('shape-rendering', 'crispEdges')
.style('pointer-events', 'none');
controlOverlay.attr('transform', function(d) {
return strTranslate(d.model.translateX, d.model.translateY);
});
var parcoordsControlView = controlOverlay.selectAll('.' + c.cn.parcoordsControlView)
.data(repeat, keyFun);
parcoordsControlView.enter()
.append('g')
.classed(c.cn.parcoordsControlView, true);
parcoordsControlView.attr('transform', function(d) {
return strTranslate(d.model.pad.l, d.model.pad.t);
});
var yAxis = parcoordsControlView.selectAll('.' + c.cn.yAxis)
.data(function(p) { return p.dimensions; }, keyFun);
yAxis.enter()
.append('g')
.classed(c.cn.yAxis, true);
parcoordsControlView.each(function(p) {
updatePanelLayout(yAxis, p, plotGlPixelRatio);
});
glLayers
.each(function(d) {
if(d.viewModel) {
if(!d.lineLayer || callbacks) { // recreate in case of having callbacks e.g. restyle. Should we test for callback to be a restyle?
d.lineLayer = lineLayerMaker(this, d);
} else d.lineLayer.update(d);
if(d.key || d.key === 0) d.viewModel[d.key] = d.lineLayer;
var setChanged = (!d.context || // don't update background
callbacks); // unless there is a callback on the context layer. Should we test the callback?
d.lineLayer.render(d.viewModel.panels, setChanged);
}
});
yAxis.attr('transform', function(d) {
return strTranslate(d.xScale(d.xIndex), 0);
});
// drag column for reordering columns
yAxis.call(d3.behavior.drag()
.origin(function(d) { return d; })
.on('drag', function(d) {
var p = d.parent;
state.linePickActive(false);
d.x = Math.max(-c.overdrag, Math.min(d.model.width + c.overdrag, d3.event.x));
d.canvasX = d.x * d.model.canvasPixelRatio;
yAxis
.sort(function(a, b) { return a.x - b.x; })
.each(function(e, i) {
e.xIndex = i;
e.x = d === e ? e.x : e.xScale(e.xIndex);
e.canvasX = e.x * e.model.canvasPixelRatio;
});
updatePanelLayout(yAxis, p, plotGlPixelRatio);
yAxis.filter(function(e) { return Math.abs(d.xIndex - e.xIndex) !== 0; })
.attr('transform', function(d) { return strTranslate(d.xScale(d.xIndex), 0); });
d3.select(this).attr('transform', strTranslate(d.x, 0));
yAxis.each(function(e, i0, i1) { if(i1 === d.parent.key) p.dimensions[i0] = e; });
p.contextLayer && p.contextLayer.render(p.panels, false, !someFiltersActive(p));
p.focusLayer.render && p.focusLayer.render(p.panels);
})
.on('dragend', function(d) {
var p = d.parent;
d.x = d.xScale(d.xIndex);
d.canvasX = d.x * d.model.canvasPixelRatio;
updatePanelLayout(yAxis, p, plotGlPixelRatio);
d3.select(this)
.attr('transform', function(d) { return strTranslate(d.x, 0); });
p.contextLayer && p.contextLayer.render(p.panels, false, !someFiltersActive(p));
p.focusLayer && p.focusLayer.render(p.panels);
p.pickLayer && p.pickLayer.render(p.panels, true);
state.linePickActive(true);
if(callbacks && callbacks.axesMoved) {
callbacks.axesMoved(p.key, p.dimensions.map(function(e) {return e.crossfilterDimensionIndex;}));
}
})
);
yAxis.exit()
.remove();
var axisOverlays = yAxis.selectAll('.' + c.cn.axisOverlays)
.data(repeat, keyFun);
axisOverlays.enter()
.append('g')
.classed(c.cn.axisOverlays, true);
axisOverlays.selectAll('.' + c.cn.axis).remove();
var axis = axisOverlays.selectAll('.' + c.cn.axis)
.data(repeat, keyFun);
axis.enter()
.append('g')
.classed(c.cn.axis, true);
axis
.each(function(d) {
var wantedTickCount = d.model.height / d.model.tickDistance;
var scale = d.domainScale;
var sdom = scale.domain();
d3.select(this)
.call(d3.svg.axis()
.orient('left')
.tickSize(4)
.outerTickSize(2)
.ticks(wantedTickCount, d.tickFormat) // works for continuous scales only...
.tickValues(d.ordinal ? // and this works for ordinal scales
sdom :
null)
.tickFormat(function(v) {
return helpers.isOrdinal(d) ? v : linearFormat(d.model.dimensions[d.visibleIndex], v);
})
.scale(scale));
Drawing.font(axis.selectAll('text'), d.model.tickFont);
});
axis.selectAll('.domain, .tick>line')
.attr('fill', 'none')
.attr('stroke', 'black')
.attr('stroke-opacity', 0.25)
.attr('stroke-width', '1px');
axis.selectAll('text')
.style('text-shadow', svgTextUtils.makeTextShadow(paperColor))
.style('cursor', 'default');
var axisHeading = axisOverlays.selectAll('.' + c.cn.axisHeading)
.data(repeat, keyFun);
axisHeading.enter()
.append('g')
.classed(c.cn.axisHeading, true);
var axisTitle = axisHeading.selectAll('.' + c.cn.axisTitle)
.data(repeat, keyFun);
axisTitle.enter()
.append('text')
.classed(c.cn.axisTitle, true)
.attr('text-anchor', 'middle')
.style('cursor', 'ew-resize')
.style('pointer-events', 'auto');
axisTitle
.text(function(d) { return d.label; })
.each(function(d) {
var e = d3.select(this);
Drawing.font(e, d.model.labelFont);
svgTextUtils.convertToTspans(e, gd);
})
.attr('transform', function(d) {
var tilt = calcTilt(d.model.labelAngle, d.model.labelSide);
var r = c.axisTitleOffset;
return (
(tilt.dir > 0 ? '' : strTranslate(0, 2 * r + d.model.height)) +
strRotate(tilt.degrees) +
strTranslate(-r * tilt.dx, -r * tilt.dy)
);
})
.attr('text-anchor', function(d) {
var tilt = calcTilt(d.model.labelAngle, d.model.labelSide);
var adx = Math.abs(tilt.dx);
var ady = Math.abs(tilt.dy);
if(2 * adx > ady) {
return (tilt.dir * tilt.dx < 0) ? 'start' : 'end';
} else {
return 'middle';
}
});
var axisExtent = axisOverlays.selectAll('.' + c.cn.axisExtent)
.data(repeat, keyFun);
axisExtent.enter()
.append('g')
.classed(c.cn.axisExtent, true);
var axisExtentTop = axisExtent.selectAll('.' + c.cn.axisExtentTop)
.data(repeat, keyFun);
axisExtentTop.enter()
.append('g')
.classed(c.cn.axisExtentTop, true);
axisExtentTop
.attr('transform', strTranslate(0, -c.axisExtentOffset));
var axisExtentTopText = axisExtentTop.selectAll('.' + c.cn.axisExtentTopText)
.data(repeat, keyFun);
axisExtentTopText.enter()
.append('text')
.classed(c.cn.axisExtentTopText, true)
.call(styleExtentTexts);
axisExtentTopText
.text(function(d) { return extremeText(d, true); })
.each(function(d) { Drawing.font(d3.select(this), d.model.rangeFont); });
var axisExtentBottom = axisExtent.selectAll('.' + c.cn.axisExtentBottom)
.data(repeat, keyFun);
axisExtentBottom.enter()
.append('g')
.classed(c.cn.axisExtentBottom, true);
axisExtentBottom
.attr('transform', function(d) {
return strTranslate(0, d.model.height + c.axisExtentOffset);
});
var axisExtentBottomText = axisExtentBottom.selectAll('.' + c.cn.axisExtentBottomText)
.data(repeat, keyFun);
axisExtentBottomText.enter()
.append('text')
.classed(c.cn.axisExtentBottomText, true)
.attr('dy', '0.75em')
.call(styleExtentTexts);
axisExtentBottomText
.text(function(d) { return extremeText(d, false); })
.each(function(d) { Drawing.font(d3.select(this), d.model.rangeFont); });
brush.ensureAxisBrush(axisOverlays, paperColor);
};
},{"../../components/colorscale":378,"../../components/drawing":388,"../../lib":503,"../../lib/gup":500,"../../lib/svg_text_utils":529,"../../plots/cartesian/axes":554,"./axisbrush":888,"./constants":891,"./helpers":893,"./lines":895,"@plotly/d3":58,"color-rgba":91}],898:[function(_dereq_,module,exports){
'use strict';
var parcoords = _dereq_('./parcoords');
var prepareRegl = _dereq_('../../lib/prepare_regl');
var isVisible = _dereq_('./helpers').isVisible;
function newIndex(visibleIndices, orig, dim) {
var origIndex = orig.indexOf(dim);
var currentIndex = visibleIndices.indexOf(origIndex);
if(currentIndex === -1) {
// invisible dimensions initially go to the end
currentIndex += orig.length;
}
return currentIndex;
}
function sorter(visibleIndices, orig) {
return function sorter(d1, d2) {
return (
newIndex(visibleIndices, orig, d1) -
newIndex(visibleIndices, orig, d2)
);
};
}
module.exports = function plot(gd, cdModule) {
var fullLayout = gd._fullLayout;
var success = prepareRegl(gd);
if(!success) return;
var currentDims = {};
var initialDims = {};
var fullIndices = {};
var inputIndices = {};
var size = fullLayout._size;
cdModule.forEach(function(d, i) {
var trace = d[0].trace;
fullIndices[i] = trace.index;
var iIn = inputIndices[i] = trace._fullInput.index;
currentDims[i] = gd.data[iIn].dimensions;
initialDims[i] = gd.data[iIn].dimensions.slice();
});
var filterChanged = function(i, initialDimIndex, newRanges) {
// Have updated `constraintrange` data on `gd.data` and raise `Plotly.restyle` event
// without having to incur heavy UI blocking due to an actual `Plotly.restyle` call
var dim = initialDims[i][initialDimIndex];
var newConstraints = newRanges.map(function(r) { return r.slice(); });
// Store constraint range in preGUI
// This one doesn't work if it's stored in pieces in _storeDirectGUIEdit
// because it's an array of variable dimensionality. So store the whole
// thing at once manually.
var aStr = 'dimensions[' + initialDimIndex + '].constraintrange';
var preGUI = fullLayout._tracePreGUI[gd._fullData[fullIndices[i]]._fullInput.uid];
if(preGUI[aStr] === undefined) {
var initialVal = dim.constraintrange;
preGUI[aStr] = initialVal || null;
}
var fullDimension = gd._fullData[fullIndices[i]].dimensions[initialDimIndex];
if(!newConstraints.length) {
delete dim.constraintrange;
delete fullDimension.constraintrange;
newConstraints = null;
} else {
if(newConstraints.length === 1) newConstraints = newConstraints[0];
dim.constraintrange = newConstraints;
fullDimension.constraintrange = newConstraints.slice();
// wrap in another array for restyle event data
newConstraints = [newConstraints];
}
var restyleData = {};
restyleData[aStr] = newConstraints;
gd.emit('plotly_restyle', [restyleData, [inputIndices[i]]]);
};
var hover = function(eventData) {
gd.emit('plotly_hover', eventData);
};
var unhover = function(eventData) {
gd.emit('plotly_unhover', eventData);
};
var axesMoved = function(i, visibleIndices) {
// Have updated order data on `gd.data` and raise `Plotly.restyle` event
// without having to incur heavy UI blocking due to an actual `Plotly.restyle` call
// drag&drop sorting of the visible dimensions
var orig = sorter(visibleIndices, initialDims[i].filter(isVisible));
currentDims[i].sort(orig);
// invisible dimensions are not interpreted in the context of drag&drop sorting as an invisible dimension
// cannot be dragged; they're interspersed into their original positions by this subsequent merging step
initialDims[i].filter(function(d) {return !isVisible(d);})
.sort(function(d) {
// subsequent splicing to be done left to right, otherwise indices may be incorrect
return initialDims[i].indexOf(d);
})
.forEach(function(d) {
currentDims[i].splice(currentDims[i].indexOf(d), 1); // remove from the end
currentDims[i].splice(initialDims[i].indexOf(d), 0, d); // insert at original index
});
// TODO: we can't really store this part of the interaction state
// directly as below, since it incudes data arrays. If we want to
// persist column order we may have to do something special for this
// case to just store the order itself.
// Registry.call('_storeDirectGUIEdit',
// gd.data[inputIndices[i]],
// fullLayout._tracePreGUI[gd._fullData[fullIndices[i]]._fullInput.uid],
// {dimensions: currentDims[i]}
// );
gd.emit('plotly_restyle', [{dimensions: [currentDims[i]]}, [inputIndices[i]]]);
};
parcoords(
gd,
cdModule,
{ // layout
width: size.w,
height: size.h,
margin: {
t: size.t,
r: size.r,
b: size.b,
l: size.l
}
},
{ // callbacks
filterChanged: filterChanged,
hover: hover,
unhover: unhover,
axesMoved: axesMoved
}
);
};
},{"../../lib/prepare_regl":516,"./helpers":893,"./parcoords":897}],899:[function(_dereq_,module,exports){
'use strict';
var baseAttrs = _dereq_('../../plots/attributes');
var domainAttrs = _dereq_('../../plots/domain').attributes;
var fontAttrs = _dereq_('../../plots/font_attributes');
var colorAttrs = _dereq_('../../components/color/attributes');
var hovertemplateAttrs = _dereq_('../../plots/template_attributes').hovertemplateAttrs;
var texttemplateAttrs = _dereq_('../../plots/template_attributes').texttemplateAttrs;
var extendFlat = _dereq_('../../lib/extend').extendFlat;
var textFontAttrs = fontAttrs({
editType: 'plot',
arrayOk: true,
colorEditType: 'plot',
});
module.exports = {
labels: {
valType: 'data_array',
editType: 'calc',
},
// equivalent of x0 and dx, if label is missing
label0: {
valType: 'number',
dflt: 0,
editType: 'calc',
},
dlabel: {
valType: 'number',
dflt: 1,
editType: 'calc',
},
values: {
valType: 'data_array',
editType: 'calc',
},
marker: {
colors: {
valType: 'data_array', // TODO 'color_array' ?
editType: 'calc',
},
line: {
color: {
valType: 'color',
dflt: colorAttrs.defaultLine,
arrayOk: true,
editType: 'style',
},
width: {
valType: 'number',
min: 0,
dflt: 0,
arrayOk: true,
editType: 'style',
},
editType: 'calc'
},
editType: 'calc'
},
text: {
valType: 'data_array',
editType: 'plot',
},
hovertext: {
valType: 'string',
dflt: '',
arrayOk: true,
editType: 'style',
},
// 'see eg:'
// 'https://www.e-education.psu.edu/natureofgeoinfo/sites/www.e-education.psu.edu.natureofgeoinfo/files/image/hisp_pies.gif',
// '(this example involves a map too - may someday be a whole trace type',
// 'of its own. but the point is the size of the whole pie is important.)'
scalegroup: {
valType: 'string',
dflt: '',
editType: 'calc',
},
// labels (legend is handled by plots.attributes.showlegend and layout.hiddenlabels)
textinfo: {
valType: 'flaglist',
flags: ['label', 'text', 'value', 'percent'],
extras: ['none'],
editType: 'calc',
},
hoverinfo: extendFlat({}, baseAttrs.hoverinfo, {
flags: ['label', 'text', 'value', 'percent', 'name']
}),
hovertemplate: hovertemplateAttrs({}, {
keys: ['label', 'color', 'value', 'percent', 'text']
}),
texttemplate: texttemplateAttrs({editType: 'plot'}, {
keys: ['label', 'color', 'value', 'percent', 'text']
}),
textposition: {
valType: 'enumerated',
values: ['inside', 'outside', 'auto', 'none'],
dflt: 'auto',
arrayOk: true,
editType: 'plot',
},
textfont: extendFlat({}, textFontAttrs, {
}),
insidetextorientation: {
valType: 'enumerated',
values: ['horizontal', 'radial', 'tangential', 'auto'],
dflt: 'auto',
editType: 'plot',
},
insidetextfont: extendFlat({}, textFontAttrs, {
}),
outsidetextfont: extendFlat({}, textFontAttrs, {
}),
automargin: {
valType: 'boolean',
dflt: false,
editType: 'plot',
},
title: {
text: {
valType: 'string',
dflt: '',
editType: 'plot',
},
font: extendFlat({}, textFontAttrs, {
}),
position: {
valType: 'enumerated',
values: [
'top left', 'top center', 'top right',
'middle center',
'bottom left', 'bottom center', 'bottom right'
],
editType: 'plot',
},
editType: 'plot'
},
// position and shape
domain: domainAttrs({name: 'pie', trace: true, editType: 'calc'}),
hole: {
valType: 'number',
min: 0,
max: 1,
dflt: 0,
editType: 'calc',
},
// ordering and direction
sort: {
valType: 'boolean',
dflt: true,
editType: 'calc',
},
direction: {
/**
* there are two common conventions, both of which place the first
* (largest, if sorted) slice with its left edge at 12 o'clock but
* succeeding slices follow either cw or ccw from there.
*
* see http://visage.co/data-visualization-101-pie-charts/
*/
valType: 'enumerated',
values: ['clockwise', 'counterclockwise'],
dflt: 'counterclockwise',
editType: 'calc',
},
rotation: {
valType: 'number',
min: -360,
max: 360,
dflt: 0,
editType: 'calc',
},
pull: {
valType: 'number',
min: 0,
max: 1,
dflt: 0,
arrayOk: true,
editType: 'calc',
},
_deprecated: {
title: {
valType: 'string',
dflt: '',
editType: 'calc',
},
titlefont: extendFlat({}, textFontAttrs, {
}),
titleposition: {
valType: 'enumerated',
values: [
'top left', 'top center', 'top right',
'middle center',
'bottom left', 'bottom center', 'bottom right'
],
editType: 'calc',
}
}
};
},{"../../components/color/attributes":365,"../../lib/extend":493,"../../plots/attributes":550,"../../plots/domain":584,"../../plots/font_attributes":585,"../../plots/template_attributes":633}],900:[function(_dereq_,module,exports){
'use strict';
var plots = _dereq_('../../plots/plots');
exports.name = 'pie';
exports.plot = function(gd, traces, transitionOpts, makeOnCompleteCallback) {
plots.plotBasePlot(exports.name, gd, traces, transitionOpts, makeOnCompleteCallback);
};
exports.clean = function(newFullData, newFullLayout, oldFullData, oldFullLayout) {
plots.cleanBasePlot(exports.name, newFullData, newFullLayout, oldFullData, oldFullLayout);
};
},{"../../plots/plots":619}],901:[function(_dereq_,module,exports){
'use strict';
var isNumeric = _dereq_('fast-isnumeric');
var tinycolor = _dereq_('tinycolor2');
var Color = _dereq_('../../components/color');
var extendedColorWayList = {};
function calc(gd, trace) {
var cd = [];
var fullLayout = gd._fullLayout;
var hiddenLabels = fullLayout.hiddenlabels || [];
var labels = trace.labels;
var colors = trace.marker.colors || [];
var vals = trace.values;
var len = trace._length;
var hasValues = trace._hasValues && len;
var i, pt;
if(trace.dlabel) {
labels = new Array(len);
for(i = 0; i < len; i++) {
labels[i] = String(trace.label0 + i * trace.dlabel);
}
}
var allThisTraceLabels = {};
var pullColor = makePullColorFn(fullLayout['_' + trace.type + 'colormap']);
var vTotal = 0;
var isAggregated = false;
for(i = 0; i < len; i++) {
var v, label, hidden;
if(hasValues) {
v = vals[i];
if(!isNumeric(v)) continue;
v = +v;
if(v < 0) continue;
} else v = 1;
label = labels[i];
if(label === undefined || label === '') label = i;
label = String(label);
var thisLabelIndex = allThisTraceLabels[label];
if(thisLabelIndex === undefined) {
allThisTraceLabels[label] = cd.length;
hidden = hiddenLabels.indexOf(label) !== -1;
if(!hidden) vTotal += v;
cd.push({
v: v,
label: label,
color: pullColor(colors[i], label),
i: i,
pts: [i],
hidden: hidden
});
} else {
isAggregated = true;
pt = cd[thisLabelIndex];
pt.v += v;
pt.pts.push(i);
if(!pt.hidden) vTotal += v;
if(pt.color === false && colors[i]) {
pt.color = pullColor(colors[i], label);
}
}
}
var shouldSort = (trace.type === 'funnelarea') ? isAggregated : trace.sort;
if(shouldSort) cd.sort(function(a, b) { return b.v - a.v; });
// include the sum of all values in the first point
if(cd[0]) cd[0].vTotal = vTotal;
return cd;
}
function makePullColorFn(colorMap) {
return function pullColor(color, id) {
if(!color) return false;
color = tinycolor(color);
if(!color.isValid()) return false;
color = Color.addOpacity(color, color.getAlpha());
if(!colorMap[id]) colorMap[id] = color;
return color;
};
}
/*
* `calc` filled in (and collated) explicit colors.
* Now we need to propagate these explicit colors to other traces,
* and fill in default colors.
* This is done after sorting, so we pick defaults
* in the order slices will be displayed
*/
function crossTraceCalc(gd, plotinfo) { // TODO: should we name the second argument opts?
var desiredType = (plotinfo || {}).type;
if(!desiredType) desiredType = 'pie';
var fullLayout = gd._fullLayout;
var calcdata = gd.calcdata;
var colorWay = fullLayout[desiredType + 'colorway'];
var colorMap = fullLayout['_' + desiredType + 'colormap'];
if(fullLayout['extend' + desiredType + 'colors']) {
colorWay = generateExtendedColors(colorWay, extendedColorWayList);
}
var dfltColorCount = 0;
for(var i = 0; i < calcdata.length; i++) {
var cd = calcdata[i];
var traceType = cd[0].trace.type;
if(traceType !== desiredType) continue;
for(var j = 0; j < cd.length; j++) {
var pt = cd[j];
if(pt.color === false) {
// have we seen this label and assigned a color to it in a previous trace?
if(colorMap[pt.label]) {
pt.color = colorMap[pt.label];
} else {
colorMap[pt.label] = pt.color = colorWay[dfltColorCount % colorWay.length];
dfltColorCount++;
}
}
}
}
}
/**
* pick a default color from the main default set, augmented by
* itself lighter then darker before repeating
*/
function generateExtendedColors(colorList, extendedColorWays) {
var i;
var colorString = JSON.stringify(colorList);
var colors = extendedColorWays[colorString];
if(!colors) {
colors = colorList.slice();
for(i = 0; i < colorList.length; i++) {
colors.push(tinycolor(colorList[i]).lighten(20).toHexString());
}
for(i = 0; i < colorList.length; i++) {
colors.push(tinycolor(colorList[i]).darken(20).toHexString());
}
extendedColorWays[colorString] = colors;
}
return colors;
}
module.exports = {
calc: calc,
crossTraceCalc: crossTraceCalc,
makePullColorFn: makePullColorFn,
generateExtendedColors: generateExtendedColors
};
},{"../../components/color":366,"fast-isnumeric":190,"tinycolor2":312}],902:[function(_dereq_,module,exports){
'use strict';
var isNumeric = _dereq_('fast-isnumeric');
var Lib = _dereq_('../../lib');
var attributes = _dereq_('./attributes');
var handleDomainDefaults = _dereq_('../../plots/domain').defaults;
var handleText = _dereq_('../bar/defaults').handleText;
function handleLabelsAndValues(labels, values) {
var hasLabels = Array.isArray(labels);
var hasValues = Lib.isArrayOrTypedArray(values);
var len = Math.min(
hasLabels ? labels.length : Infinity,
hasValues ? values.length : Infinity
);
if(!isFinite(len)) len = 0;
if(len && hasValues) {
var hasPositive;
for(var i = 0; i < len; i++) {
var v = values[i];
if(isNumeric(v) && v > 0) {
hasPositive = true;
break;
}
}
if(!hasPositive) len = 0;
}
return {
hasLabels: hasLabels,
hasValues: hasValues,
len: len
};
}
function supplyDefaults(traceIn, traceOut, defaultColor, layout) {
function coerce(attr, dflt) {
return Lib.coerce(traceIn, traceOut, attributes, attr, dflt);
}
var labels = coerce('labels');
var values = coerce('values');
var res = handleLabelsAndValues(labels, values);
var len = res.len;
traceOut._hasLabels = res.hasLabels;
traceOut._hasValues = res.hasValues;
if(!traceOut._hasLabels &&
traceOut._hasValues
) {
coerce('label0');
coerce('dlabel');
}
if(!len) {
traceOut.visible = false;
return;
}
traceOut._length = len;
var lineWidth = coerce('marker.line.width');
if(lineWidth) coerce('marker.line.color');
coerce('marker.colors');
coerce('scalegroup');
// TODO: hole needs to be coerced to the same value within a scaleegroup
var textData = coerce('text');
var textTemplate = coerce('texttemplate');
var textInfo;
if(!textTemplate) textInfo = coerce('textinfo', Array.isArray(textData) ? 'text+percent' : 'percent');
coerce('hovertext');
coerce('hovertemplate');
if(textTemplate || (textInfo && textInfo !== 'none')) {
var textposition = coerce('textposition');
handleText(traceIn, traceOut, layout, coerce, textposition, {
moduleHasSelected: false,
moduleHasUnselected: false,
moduleHasConstrain: false,
moduleHasCliponaxis: false,
moduleHasTextangle: false,
moduleHasInsideanchor: false
});
var hasBoth = Array.isArray(textposition) || textposition === 'auto';
var hasOutside = hasBoth || textposition === 'outside';
if(hasOutside) {
coerce('automargin');
}
if(textposition === 'inside' || textposition === 'auto' || Array.isArray(textposition)) {
coerce('insidetextorientation');
}
}
handleDomainDefaults(traceOut, layout, coerce);
var hole = coerce('hole');
var title = coerce('title.text');
if(title) {
var titlePosition = coerce('title.position', hole ? 'middle center' : 'top center');
if(!hole && titlePosition === 'middle center') traceOut.title.position = 'top center';
Lib.coerceFont(coerce, 'title.font', layout.font);
}
coerce('sort');
coerce('direction');
coerce('rotation');
coerce('pull');
}
module.exports = {
handleLabelsAndValues: handleLabelsAndValues,
supplyDefaults: supplyDefaults
};
},{"../../lib":503,"../../plots/domain":584,"../bar/defaults":652,"./attributes":899,"fast-isnumeric":190}],903:[function(_dereq_,module,exports){
'use strict';
var appendArrayMultiPointValues = _dereq_('../../components/fx/helpers').appendArrayMultiPointValues;
// Note: like other eventData routines, this creates the data for hover/unhover/click events
// but it has a different API and goes through a totally different pathway.
// So to ensure it doesn't get misused, it's not attached to the Pie module.
module.exports = function eventData(pt, trace) {
var out = {
curveNumber: trace.index,
pointNumbers: pt.pts,
data: trace._input,
fullData: trace,
label: pt.label,
color: pt.color,
value: pt.v,
percent: pt.percent,
text: pt.text,
bbox: pt.bbox,
// pt.v (and pt.i below) for backward compatibility
v: pt.v
};
// Only include pointNumber if it's unambiguous
if(pt.pts.length === 1) out.pointNumber = out.i = pt.pts[0];
// Add extra data arrays to the output
// notice that this is the multi-point version ('s' on the end!)
// so added data will be arrays matching the pointNumbers array.
appendArrayMultiPointValues(out, trace, pt.pts);
// don't include obsolete fields in new funnelarea traces
if(trace.type === 'funnelarea') {
delete out.v;
delete out.i;
}
return out;
};
},{"../../components/fx/helpers":402}],904:[function(_dereq_,module,exports){
'use strict';
var Lib = _dereq_('../../lib');
function format(vRounded) {
return (
vRounded.indexOf('e') !== -1 ? vRounded.replace(/[.]?0+e/, 'e') :
vRounded.indexOf('.') !== -1 ? vRounded.replace(/[.]?0+$/, '') :
vRounded
);
}
exports.formatPiePercent = function formatPiePercent(v, separators) {
var vRounded = format((v * 100).toPrecision(3));
return Lib.numSeparate(vRounded, separators) + '%';
};
exports.formatPieValue = function formatPieValue(v, separators) {
var vRounded = format(v.toPrecision(10));
return Lib.numSeparate(vRounded, separators);
};
exports.getFirstFilled = function getFirstFilled(array, indices) {
if(!Array.isArray(array)) return;
for(var i = 0; i < indices.length; i++) {
var v = array[indices[i]];
if(v || v === 0 || v === '') return v;
}
};
exports.castOption = function castOption(item, indices) {
if(Array.isArray(item)) return exports.getFirstFilled(item, indices);
else if(item) return item;
};
exports.getRotationAngle = function(rotation) {
return (rotation === 'auto' ? 0 : rotation) * Math.PI / 180;
};
},{"../../lib":503}],905:[function(_dereq_,module,exports){
'use strict';
module.exports = {
attributes: _dereq_('./attributes'),
supplyDefaults: _dereq_('./defaults').supplyDefaults,
supplyLayoutDefaults: _dereq_('./layout_defaults'),
layoutAttributes: _dereq_('./layout_attributes'),
calc: _dereq_('./calc').calc,
crossTraceCalc: _dereq_('./calc').crossTraceCalc,
plot: _dereq_('./plot').plot,
style: _dereq_('./style'),
styleOne: _dereq_('./style_one'),
moduleType: 'trace',
name: 'pie',
basePlotModule: _dereq_('./base_plot'),
categories: ['pie-like', 'pie', 'showLegend'],
meta: {
}
};
},{"./attributes":899,"./base_plot":900,"./calc":901,"./defaults":902,"./layout_attributes":906,"./layout_defaults":907,"./plot":908,"./style":909,"./style_one":910}],906:[function(_dereq_,module,exports){
'use strict';
module.exports = {
hiddenlabels: {
valType: 'data_array',
editType: 'calc',
},
piecolorway: {
valType: 'colorlist',
editType: 'calc',
},
extendpiecolors: {
valType: 'boolean',
dflt: true,
editType: 'calc',
}
};
},{}],907:[function(_dereq_,module,exports){
'use strict';
var Lib = _dereq_('../../lib');
var layoutAttributes = _dereq_('./layout_attributes');
module.exports = function supplyLayoutDefaults(layoutIn, layoutOut) {
function coerce(attr, dflt) {
return Lib.coerce(layoutIn, layoutOut, layoutAttributes, attr, dflt);
}
coerce('hiddenlabels');
coerce('piecolorway', layoutOut.colorway);
coerce('extendpiecolors');
};
},{"../../lib":503,"./layout_attributes":906}],908:[function(_dereq_,module,exports){
'use strict';
var d3 = _dereq_('@plotly/d3');
var Plots = _dereq_('../../plots/plots');
var Fx = _dereq_('../../components/fx');
var Color = _dereq_('../../components/color');
var Drawing = _dereq_('../../components/drawing');
var Lib = _dereq_('../../lib');
var strScale = Lib.strScale;
var strTranslate = Lib.strTranslate;
var svgTextUtils = _dereq_('../../lib/svg_text_utils');
var uniformText = _dereq_('../bar/uniform_text');
var recordMinTextSize = uniformText.recordMinTextSize;
var clearMinTextSize = uniformText.clearMinTextSize;
var TEXTPAD = _dereq_('../bar/constants').TEXTPAD;
var helpers = _dereq_('./helpers');
var eventData = _dereq_('./event_data');
var isValidTextValue = _dereq_('../../lib').isValidTextValue;
function plot(gd, cdModule) {
var fullLayout = gd._fullLayout;
var gs = fullLayout._size;
clearMinTextSize('pie', fullLayout);
prerenderTitles(cdModule, gd);
layoutAreas(cdModule, gs);
var plotGroups = Lib.makeTraceGroups(fullLayout._pielayer, cdModule, 'trace').each(function(cd) {
var plotGroup = d3.select(this);
var cd0 = cd[0];
var trace = cd0.trace;
setCoords(cd);
// TODO: miter might look better but can sometimes cause problems
// maybe miter with a small-ish stroke-miterlimit?
plotGroup.attr('stroke-linejoin', 'round');
plotGroup.each(function() {
var slices = d3.select(this).selectAll('g.slice').data(cd);
slices.enter().append('g')
.classed('slice', true);
slices.exit().remove();
var quadrants = [
[[], []], // y<0: x<0, x>=0
[[], []] // y>=0: x<0, x>=0
];
var hasOutsideText = false;
slices.each(function(pt, i) {
if(pt.hidden) {
d3.select(this).selectAll('path,g').remove();
return;
}
// to have consistent event data compared to other traces
pt.pointNumber = pt.i;
pt.curveNumber = trace.index;
quadrants[pt.pxmid[1] < 0 ? 0 : 1][pt.pxmid[0] < 0 ? 0 : 1].push(pt);
var cx = cd0.cx;
var cy = cd0.cy;
var sliceTop = d3.select(this);
var slicePath = sliceTop.selectAll('path.surface').data([pt]);
slicePath.enter().append('path')
.classed('surface', true)
.style({'pointer-events': 'all'});
sliceTop.call(attachFxHandlers, gd, cd);
if(trace.pull) {
var pull = +helpers.castOption(trace.pull, pt.pts) || 0;
if(pull > 0) {
cx += pull * pt.pxmid[0];
cy += pull * pt.pxmid[1];
}
}
pt.cxFinal = cx;
pt.cyFinal = cy;
function arc(start, finish, cw, scale) {
var dx = scale * (finish[0] - start[0]);
var dy = scale * (finish[1] - start[1]);
return 'a' +
(scale * cd0.r) + ',' + (scale * cd0.r) + ' 0 ' +
pt.largeArc + (cw ? ' 1 ' : ' 0 ') + dx + ',' + dy;
}
var hole = trace.hole;
if(pt.v === cd0.vTotal) { // 100% fails bcs arc start and end are identical
var outerCircle = 'M' + (cx + pt.px0[0]) + ',' + (cy + pt.px0[1]) +
arc(pt.px0, pt.pxmid, true, 1) +
arc(pt.pxmid, pt.px0, true, 1) + 'Z';
if(hole) {
slicePath.attr('d',
'M' + (cx + hole * pt.px0[0]) + ',' + (cy + hole * pt.px0[1]) +
arc(pt.px0, pt.pxmid, false, hole) +
arc(pt.pxmid, pt.px0, false, hole) +
'Z' + outerCircle);
} else slicePath.attr('d', outerCircle);
} else {
var outerArc = arc(pt.px0, pt.px1, true, 1);
if(hole) {
var rim = 1 - hole;
slicePath.attr('d',
'M' + (cx + hole * pt.px1[0]) + ',' + (cy + hole * pt.px1[1]) +
arc(pt.px1, pt.px0, false, hole) +
'l' + (rim * pt.px0[0]) + ',' + (rim * pt.px0[1]) +
outerArc +
'Z');
} else {
slicePath.attr('d',
'M' + cx + ',' + cy +
'l' + pt.px0[0] + ',' + pt.px0[1] +
outerArc +
'Z');
}
}
// add text
formatSliceLabel(gd, pt, cd0);
var textPosition = helpers.castOption(trace.textposition, pt.pts);
var sliceTextGroup = sliceTop.selectAll('g.slicetext')
.data(pt.text && (textPosition !== 'none') ? [0] : []);
sliceTextGroup.enter().append('g')
.classed('slicetext', true);
sliceTextGroup.exit().remove();
sliceTextGroup.each(function() {
var sliceText = Lib.ensureSingle(d3.select(this), 'text', '', function(s) {
// prohibit tex interpretation until we can handle
// tex and regular text together
s.attr('data-notex', 1);
});
var font = Lib.ensureUniformFontSize(gd, textPosition === 'outside' ?
determineOutsideTextFont(trace, pt, fullLayout.font) :
determineInsideTextFont(trace, pt, fullLayout.font)
);
sliceText.text(pt.text)
.attr({
'class': 'slicetext',
transform: '',
'text-anchor': 'middle'
})
.call(Drawing.font, font)
.call(svgTextUtils.convertToTspans, gd);
// position the text relative to the slice
var textBB = Drawing.bBox(sliceText.node());
var transform;
if(textPosition === 'outside') {
transform = transformOutsideText(textBB, pt);
} else {
transform = transformInsideText(textBB, pt, cd0);
if(textPosition === 'auto' && transform.scale < 1) {
var newFont = Lib.ensureUniformFontSize(gd, trace.outsidetextfont);
sliceText.call(Drawing.font, newFont);
textBB = Drawing.bBox(sliceText.node());
transform = transformOutsideText(textBB, pt);
}
}
var textPosAngle = transform.textPosAngle;
var textXY = textPosAngle === undefined ? pt.pxmid : getCoords(cd0.r, textPosAngle);
transform.targetX = cx + textXY[0] * transform.rCenter + (transform.x || 0);
transform.targetY = cy + textXY[1] * transform.rCenter + (transform.y || 0);
computeTransform(transform, textBB);
// save some stuff to use later ensure no labels overlap
if(transform.outside) {
var targetY = transform.targetY;
pt.yLabelMin = targetY - textBB.height / 2;
pt.yLabelMid = targetY;
pt.yLabelMax = targetY + textBB.height / 2;
pt.labelExtraX = 0;
pt.labelExtraY = 0;
hasOutsideText = true;
}
transform.fontSize = font.size;
recordMinTextSize(trace.type, transform, fullLayout);
cd[i].transform = transform;
sliceText.attr('transform', Lib.getTextTransform(transform));
});
});
// add the title
var titleTextGroup = d3.select(this).selectAll('g.titletext')
.data(trace.title.text ? [0] : []);
titleTextGroup.enter().append('g')
.classed('titletext', true);
titleTextGroup.exit().remove();
titleTextGroup.each(function() {
var titleText = Lib.ensureSingle(d3.select(this), 'text', '', function(s) {
// prohibit tex interpretation as above
s.attr('data-notex', 1);
});
var txt = trace.title.text;
if(trace._meta) {
txt = Lib.templateString(txt, trace._meta);
}
titleText.text(txt)
.attr({
'class': 'titletext',
transform: '',
'text-anchor': 'middle',
})
.call(Drawing.font, trace.title.font)
.call(svgTextUtils.convertToTspans, gd);
var transform;
if(trace.title.position === 'middle center') {
transform = positionTitleInside(cd0);
} else {
transform = positionTitleOutside(cd0, gs);
}
titleText.attr('transform',
strTranslate(transform.x, transform.y) +
strScale(Math.min(1, transform.scale)) +
strTranslate(transform.tx, transform.ty));
});
// now make sure no labels overlap (at least within one pie)
if(hasOutsideText) scootLabels(quadrants, trace);
plotTextLines(slices, trace);
if(hasOutsideText && trace.automargin) {
// TODO if we ever want to improve perf,
// we could reuse the textBB computed above together
// with the sliceText transform info
var traceBbox = Drawing.bBox(plotGroup.node());
var domain = trace.domain;
var vpw = gs.w * (domain.x[1] - domain.x[0]);
var vph = gs.h * (domain.y[1] - domain.y[0]);
var xgap = (0.5 * vpw - cd0.r) / gs.w;
var ygap = (0.5 * vph - cd0.r) / gs.h;
Plots.autoMargin(gd, 'pie.' + trace.uid + '.automargin', {
xl: domain.x[0] - xgap,
xr: domain.x[1] + xgap,
yb: domain.y[0] - ygap,
yt: domain.y[1] + ygap,
l: Math.max(cd0.cx - cd0.r - traceBbox.left, 0),
r: Math.max(traceBbox.right - (cd0.cx + cd0.r), 0),
b: Math.max(traceBbox.bottom - (cd0.cy + cd0.r), 0),
t: Math.max(cd0.cy - cd0.r - traceBbox.top, 0),
pad: 5
});
}
});
});
// This is for a bug in Chrome (as of 2015-07-22, and does not affect FF)
// if insidetextfont and outsidetextfont are different sizes, sometimes the size
// of an "em" gets taken from the wrong element at first so lines are
// spaced wrong. You just have to tell it to try again later and it gets fixed.
// I have no idea why we haven't seen this in other contexts. Also, sometimes
// it gets the initial draw correct but on redraw it gets confused.
setTimeout(function() {
plotGroups.selectAll('tspan').each(function() {
var s = d3.select(this);
if(s.attr('dy')) s.attr('dy', s.attr('dy'));
});
}, 0);
}
// TODO add support for transition
function plotTextLines(slices, trace) {
slices.each(function(pt) {
var sliceTop = d3.select(this);
if(!pt.labelExtraX && !pt.labelExtraY) {
sliceTop.select('path.textline').remove();
return;
}
// first move the text to its new location
var sliceText = sliceTop.select('g.slicetext text');
pt.transform.targetX += pt.labelExtraX;
pt.transform.targetY += pt.labelExtraY;
sliceText.attr('transform', Lib.getTextTransform(pt.transform));
// then add a line to the new location
var lineStartX = pt.cxFinal + pt.pxmid[0];
var lineStartY = pt.cyFinal + pt.pxmid[1];
var textLinePath = 'M' + lineStartX + ',' + lineStartY;
var finalX = (pt.yLabelMax - pt.yLabelMin) * (pt.pxmid[0] < 0 ? -1 : 1) / 4;
if(pt.labelExtraX) {
var yFromX = pt.labelExtraX * pt.pxmid[1] / pt.pxmid[0];
var yNet = pt.yLabelMid + pt.labelExtraY - (pt.cyFinal + pt.pxmid[1]);
if(Math.abs(yFromX) > Math.abs(yNet)) {
textLinePath +=
'l' + (yNet * pt.pxmid[0] / pt.pxmid[1]) + ',' + yNet +
'H' + (lineStartX + pt.labelExtraX + finalX);
} else {
textLinePath += 'l' + pt.labelExtraX + ',' + yFromX +
'v' + (yNet - yFromX) +
'h' + finalX;
}
} else {
textLinePath +=
'V' + (pt.yLabelMid + pt.labelExtraY) +
'h' + finalX;
}
Lib.ensureSingle(sliceTop, 'path', 'textline')
.call(Color.stroke, trace.outsidetextfont.color)
.attr({
'stroke-width': Math.min(2, trace.outsidetextfont.size / 8),
d: textLinePath,
fill: 'none'
});
});
}
function attachFxHandlers(sliceTop, gd, cd) {
var cd0 = cd[0];
var cx = cd0.cx;
var cy = cd0.cy;
var trace = cd0.trace;
var isFunnelArea = trace.type === 'funnelarea';
// hover state vars
// have we drawn a hover label, so it should be cleared later
if(!('_hasHoverLabel' in trace)) trace._hasHoverLabel = false;
// have we emitted a hover event, so later an unhover event should be emitted
// note that click events do not depend on this - you can still get them
// with hovermode: false or if you were earlier dragging, then clicked
// in the same slice that you moused up in
if(!('_hasHoverEvent' in trace)) trace._hasHoverEvent = false;
sliceTop.on('mouseover', function(pt) {
// in case fullLayout or fullData has changed without a replot
var fullLayout2 = gd._fullLayout;
var trace2 = gd._fullData[trace.index];
if(gd._dragging || fullLayout2.hovermode === false) return;
var hoverinfo = trace2.hoverinfo;
if(Array.isArray(hoverinfo)) {
// super hacky: we need to pull out the *first* hoverinfo from
// pt.pts, then put it back into an array in a dummy trace
// and call castHoverinfo on that.
// TODO: do we want to have Fx.castHoverinfo somehow handle this?
// it already takes an array for index, for 2D, so this seems tricky.
hoverinfo = Fx.castHoverinfo({
hoverinfo: [helpers.castOption(hoverinfo, pt.pts)],
_module: trace._module
}, fullLayout2, 0);
}
if(hoverinfo === 'all') hoverinfo = 'label+text+value+percent+name';
// in case we dragged over the pie from another subplot,
// or if hover is turned off
if(trace2.hovertemplate || (hoverinfo !== 'none' && hoverinfo !== 'skip' && hoverinfo)) {
var rInscribed = pt.rInscribed || 0;
var hoverCenterX = cx + pt.pxmid[0] * (1 - rInscribed);
var hoverCenterY = cy + pt.pxmid[1] * (1 - rInscribed);
var separators = fullLayout2.separators;
var text = [];
if(hoverinfo && hoverinfo.indexOf('label') !== -1) text.push(pt.label);
pt.text = helpers.castOption(trace2.hovertext || trace2.text, pt.pts);
if(hoverinfo && hoverinfo.indexOf('text') !== -1) {
var tx = pt.text;
if(Lib.isValidTextValue(tx)) text.push(tx);
}
pt.value = pt.v;
pt.valueLabel = helpers.formatPieValue(pt.v, separators);
if(hoverinfo && hoverinfo.indexOf('value') !== -1) text.push(pt.valueLabel);
pt.percent = pt.v / cd0.vTotal;
pt.percentLabel = helpers.formatPiePercent(pt.percent, separators);
if(hoverinfo && hoverinfo.indexOf('percent') !== -1) text.push(pt.percentLabel);
var hoverLabel = trace2.hoverlabel;
var hoverFont = hoverLabel.font;
var bbox = [];
Fx.loneHover({
trace: trace,
x0: hoverCenterX - rInscribed * cd0.r,
x1: hoverCenterX + rInscribed * cd0.r,
y: hoverCenterY,
_x0: isFunnelArea ? cx + pt.TL[0] : hoverCenterX - rInscribed * cd0.r,
_x1: isFunnelArea ? cx + pt.TR[0] : hoverCenterX + rInscribed * cd0.r,
_y0: isFunnelArea ? cy + pt.TL[1] : hoverCenterY - rInscribed * cd0.r,
_y1: isFunnelArea ? cy + pt.BL[1] : hoverCenterY + rInscribed * cd0.r,
text: text.join('
'),
name: (trace2.hovertemplate || hoverinfo.indexOf('name') !== -1) ? trace2.name : undefined,
idealAlign: pt.pxmid[0] < 0 ? 'left' : 'right',
color: helpers.castOption(hoverLabel.bgcolor, pt.pts) || pt.color,
borderColor: helpers.castOption(hoverLabel.bordercolor, pt.pts),
fontFamily: helpers.castOption(hoverFont.family, pt.pts),
fontSize: helpers.castOption(hoverFont.size, pt.pts),
fontColor: helpers.castOption(hoverFont.color, pt.pts),
nameLength: helpers.castOption(hoverLabel.namelength, pt.pts),
textAlign: helpers.castOption(hoverLabel.align, pt.pts),
hovertemplate: helpers.castOption(trace2.hovertemplate, pt.pts),
hovertemplateLabels: pt,
eventData: [eventData(pt, trace2)]
}, {
container: fullLayout2._hoverlayer.node(),
outerContainer: fullLayout2._paper.node(),
gd: gd,
inOut_bbox: bbox
});
pt.bbox = bbox[0];
trace._hasHoverLabel = true;
}
trace._hasHoverEvent = true;
gd.emit('plotly_hover', {
points: [eventData(pt, trace2)],
event: d3.event
});
});
sliceTop.on('mouseout', function(evt) {
var fullLayout2 = gd._fullLayout;
var trace2 = gd._fullData[trace.index];
var pt = d3.select(this).datum();
if(trace._hasHoverEvent) {
evt.originalEvent = d3.event;
gd.emit('plotly_unhover', {
points: [eventData(pt, trace2)],
event: d3.event
});
trace._hasHoverEvent = false;
}
if(trace._hasHoverLabel) {
Fx.loneUnhover(fullLayout2._hoverlayer.node());
trace._hasHoverLabel = false;
}
});
sliceTop.on('click', function(pt) {
// TODO: this does not support right-click. If we want to support it, we
// would likely need to change pie to use dragElement instead of straight
// mapbox event binding. Or perhaps better, make a simple wrapper with the
// right mousedown, mousemove, and mouseup handlers just for a left/right click
// mapbox would use this too.
var fullLayout2 = gd._fullLayout;
var trace2 = gd._fullData[trace.index];
if(gd._dragging || fullLayout2.hovermode === false) return;
gd._hoverdata = [eventData(pt, trace2)];
Fx.click(gd, d3.event);
});
}
function determineOutsideTextFont(trace, pt, layoutFont) {
var color =
helpers.castOption(trace.outsidetextfont.color, pt.pts) ||
helpers.castOption(trace.textfont.color, pt.pts) ||
layoutFont.color;
var family =
helpers.castOption(trace.outsidetextfont.family, pt.pts) ||
helpers.castOption(trace.textfont.family, pt.pts) ||
layoutFont.family;
var size =
helpers.castOption(trace.outsidetextfont.size, pt.pts) ||
helpers.castOption(trace.textfont.size, pt.pts) ||
layoutFont.size;
return {
color: color,
family: family,
size: size
};
}
function determineInsideTextFont(trace, pt, layoutFont) {
var customColor = helpers.castOption(trace.insidetextfont.color, pt.pts);
if(!customColor && trace._input.textfont) {
// Why not simply using trace.textfont? Because if not set, it
// defaults to layout.font which has a default color. But if
// textfont.color and insidetextfont.color don't supply a value,
// a contrasting color shall be used.
customColor = helpers.castOption(trace._input.textfont.color, pt.pts);
}
var family =
helpers.castOption(trace.insidetextfont.family, pt.pts) ||
helpers.castOption(trace.textfont.family, pt.pts) ||
layoutFont.family;
var size =
helpers.castOption(trace.insidetextfont.size, pt.pts) ||
helpers.castOption(trace.textfont.size, pt.pts) ||
layoutFont.size;
return {
color: customColor || Color.contrast(pt.color),
family: family,
size: size
};
}
function prerenderTitles(cdModule, gd) {
var cd0, trace;
// Determine the width and height of the title for each pie.
for(var i = 0; i < cdModule.length; i++) {
cd0 = cdModule[i][0];
trace = cd0.trace;
if(trace.title.text) {
var txt = trace.title.text;
if(trace._meta) {
txt = Lib.templateString(txt, trace._meta);
}
var dummyTitle = Drawing.tester.append('text')
.attr('data-notex', 1)
.text(txt)
.call(Drawing.font, trace.title.font)
.call(svgTextUtils.convertToTspans, gd);
var bBox = Drawing.bBox(dummyTitle.node(), true);
cd0.titleBox = {
width: bBox.width,
height: bBox.height,
};
dummyTitle.remove();
}
}
}
function transformInsideText(textBB, pt, cd0) {
var r = cd0.r || pt.rpx1;
var rInscribed = pt.rInscribed;
var isEmpty = pt.startangle === pt.stopangle;
if(isEmpty) {
return {
rCenter: 1 - rInscribed,
scale: 0,
rotate: 0,
textPosAngle: 0
};
}
var ring = pt.ring;
var isCircle = (ring === 1) && (Math.abs(pt.startangle - pt.stopangle) === Math.PI * 2);
var halfAngle = pt.halfangle;
var midAngle = pt.midangle;
var orientation = cd0.trace.insidetextorientation;
var isHorizontal = orientation === 'horizontal';
var isTangential = orientation === 'tangential';
var isRadial = orientation === 'radial';
var isAuto = orientation === 'auto';
var allTransforms = [];
var newT;
if(!isAuto) {
// max size if text is placed (horizontally) at the top or bottom of the arc
var considerCrossing = function(angle, key) {
if(isCrossing(pt, angle)) {
var dStart = Math.abs(angle - pt.startangle);
var dStop = Math.abs(angle - pt.stopangle);
var closestEdge = dStart < dStop ? dStart : dStop;
if(key === 'tan') {
newT = calcTanTransform(textBB, r, ring, closestEdge, 0);
} else { // case of 'rad'
newT = calcRadTransform(textBB, r, ring, closestEdge, Math.PI / 2);
}
newT.textPosAngle = angle;
allTransforms.push(newT);
}
};
// to cover all cases with trace.rotation added
var i;
if(isHorizontal || isTangential) {
// top
for(i = 4; i >= -4; i -= 2) considerCrossing(Math.PI * i, 'tan');
// bottom
for(i = 4; i >= -4; i -= 2) considerCrossing(Math.PI * (i + 1), 'tan');
}
if(isHorizontal || isRadial) {
// left
for(i = 4; i >= -4; i -= 2) considerCrossing(Math.PI * (i + 1.5), 'rad');
// right
for(i = 4; i >= -4; i -= 2) considerCrossing(Math.PI * (i + 0.5), 'rad');
}
}
if(isCircle || isAuto || isHorizontal) {
// max size text can be inserted inside without rotating it
// this inscribes the text rectangle in a circle, which is then inscribed
// in the slice, so it will be an underestimate, which some day we may want
// to improve so this case can get more use
var textDiameter = Math.sqrt(textBB.width * textBB.width + textBB.height * textBB.height);
newT = {
scale: rInscribed * r * 2 / textDiameter,
// and the center position and rotation in this case
rCenter: 1 - rInscribed,
rotate: 0
};
newT.textPosAngle = (pt.startangle + pt.stopangle) / 2;
if(newT.scale >= 1) return newT;
allTransforms.push(newT);
}
if(isAuto || isRadial) {
newT = calcRadTransform(textBB, r, ring, halfAngle, midAngle);
newT.textPosAngle = (pt.startangle + pt.stopangle) / 2;
allTransforms.push(newT);
}
if(isAuto || isTangential) {
newT = calcTanTransform(textBB, r, ring, halfAngle, midAngle);
newT.textPosAngle = (pt.startangle + pt.stopangle) / 2;
allTransforms.push(newT);
}
var id = 0;
var maxScale = 0;
for(var k = 0; k < allTransforms.length; k++) {
var s = allTransforms[k].scale;
if(maxScale < s) {
maxScale = s;
id = k;
}
if(!isAuto && maxScale >= 1) {
// respect test order for non-auto options
break;
}
}
return allTransforms[id];
}
function isCrossing(pt, angle) {
var start = pt.startangle;
var stop = pt.stopangle;
return (
(start > angle && angle > stop) ||
(start < angle && angle < stop)
);
}
function calcRadTransform(textBB, r, ring, halfAngle, midAngle) {
r = Math.max(0, r - 2 * TEXTPAD);
// max size if text is rotated radially
var a = textBB.width / textBB.height;
var s = calcMaxHalfSize(a, halfAngle, r, ring);
return {
scale: s * 2 / textBB.height,
rCenter: calcRCenter(a, s / r),
rotate: calcRotate(midAngle)
};
}
function calcTanTransform(textBB, r, ring, halfAngle, midAngle) {
r = Math.max(0, r - 2 * TEXTPAD);
// max size if text is rotated tangentially
var a = textBB.height / textBB.width;
var s = calcMaxHalfSize(a, halfAngle, r, ring);
return {
scale: s * 2 / textBB.width,
rCenter: calcRCenter(a, s / r),
rotate: calcRotate(midAngle + Math.PI / 2)
};
}
function calcRCenter(a, b) {
return Math.cos(b) - a * b;
}
function calcRotate(t) {
return (180 / Math.PI * t + 720) % 180 - 90;
}
function calcMaxHalfSize(a, halfAngle, r, ring) {
var q = a + 1 / (2 * Math.tan(halfAngle));
return r * Math.min(
1 / (Math.sqrt(q * q + 0.5) + q),
ring / (Math.sqrt(a * a + ring / 2) + a)
);
}
function getInscribedRadiusFraction(pt, cd0) {
if(pt.v === cd0.vTotal && !cd0.trace.hole) return 1;// special case of 100% with no hole
return Math.min(1 / (1 + 1 / Math.sin(pt.halfangle)), pt.ring / 2);
}
function transformOutsideText(textBB, pt) {
var x = pt.pxmid[0];
var y = pt.pxmid[1];
var dx = textBB.width / 2;
var dy = textBB.height / 2;
if(x < 0) dx *= -1;
if(y < 0) dy *= -1;
return {
scale: 1,
rCenter: 1,
rotate: 0,
x: dx + Math.abs(dy) * (dx > 0 ? 1 : -1) / 2,
y: dy / (1 + x * x / (y * y)),
outside: true
};
}
function positionTitleInside(cd0) {
var textDiameter =
Math.sqrt(cd0.titleBox.width * cd0.titleBox.width + cd0.titleBox.height * cd0.titleBox.height);
return {
x: cd0.cx,
y: cd0.cy,
scale: cd0.trace.hole * cd0.r * 2 / textDiameter,
tx: 0,
ty: - cd0.titleBox.height / 2 + cd0.trace.title.font.size
};
}
function positionTitleOutside(cd0, plotSize) {
var scaleX = 1;
var scaleY = 1;
var maxPull;
var trace = cd0.trace;
// position of the baseline point of the text box in the plot, before scaling.
// we anchored the text in the middle, so the baseline is on the bottom middle
// of the first line of text.
var topMiddle = {
x: cd0.cx,
y: cd0.cy
};
// relative translation of the text box after scaling
var translate = {
tx: 0,
ty: 0
};
// we reason below as if the baseline is the top middle point of the text box.
// so we must add the font size to approximate the y-coord. of the top.
// note that this correction must happen after scaling.
translate.ty += trace.title.font.size;
maxPull = getMaxPull(trace);
if(trace.title.position.indexOf('top') !== -1) {
topMiddle.y -= (1 + maxPull) * cd0.r;
translate.ty -= cd0.titleBox.height;
} else if(trace.title.position.indexOf('bottom') !== -1) {
topMiddle.y += (1 + maxPull) * cd0.r;
}
var rx = applyAspectRatio(cd0.r, cd0.trace.aspectratio);
var maxWidth = plotSize.w * (trace.domain.x[1] - trace.domain.x[0]) / 2;
if(trace.title.position.indexOf('left') !== -1) {
// we start the text at the left edge of the pie
maxWidth = maxWidth + rx;
topMiddle.x -= (1 + maxPull) * rx;
translate.tx += cd0.titleBox.width / 2;
} else if(trace.title.position.indexOf('center') !== -1) {
maxWidth *= 2;
} else if(trace.title.position.indexOf('right') !== -1) {
maxWidth = maxWidth + rx;
topMiddle.x += (1 + maxPull) * rx;
translate.tx -= cd0.titleBox.width / 2;
}
scaleX = maxWidth / cd0.titleBox.width;
scaleY = getTitleSpace(cd0, plotSize) / cd0.titleBox.height;
return {
x: topMiddle.x,
y: topMiddle.y,
scale: Math.min(scaleX, scaleY),
tx: translate.tx,
ty: translate.ty
};
}
function applyAspectRatio(x, aspectratio) {
return x / ((aspectratio === undefined) ? 1 : aspectratio);
}
function getTitleSpace(cd0, plotSize) {
var trace = cd0.trace;
var pieBoxHeight = plotSize.h * (trace.domain.y[1] - trace.domain.y[0]);
// use at most half of the plot for the title
return Math.min(cd0.titleBox.height, pieBoxHeight / 2);
}
function getMaxPull(trace) {
var maxPull = trace.pull;
if(!maxPull) return 0;
var j;
if(Array.isArray(maxPull)) {
maxPull = 0;
for(j = 0; j < trace.pull.length; j++) {
if(trace.pull[j] > maxPull) maxPull = trace.pull[j];
}
}
return maxPull;
}
function scootLabels(quadrants, trace) {
var xHalf, yHalf, equatorFirst, farthestX, farthestY,
xDiffSign, yDiffSign, thisQuad, oppositeQuad,
wholeSide, i, thisQuadOutside, firstOppositeOutsidePt;
function topFirst(a, b) { return a.pxmid[1] - b.pxmid[1]; }
function bottomFirst(a, b) { return b.pxmid[1] - a.pxmid[1]; }
function scootOneLabel(thisPt, prevPt) {
if(!prevPt) prevPt = {};
var prevOuterY = prevPt.labelExtraY + (yHalf ? prevPt.yLabelMax : prevPt.yLabelMin);
var thisInnerY = yHalf ? thisPt.yLabelMin : thisPt.yLabelMax;
var thisOuterY = yHalf ? thisPt.yLabelMax : thisPt.yLabelMin;
var thisSliceOuterY = thisPt.cyFinal + farthestY(thisPt.px0[1], thisPt.px1[1]);
var newExtraY = prevOuterY - thisInnerY;
var xBuffer, i, otherPt, otherOuterY, otherOuterX, newExtraX;
// make sure this label doesn't overlap other labels
// this *only* has us move these labels vertically
if(newExtraY * yDiffSign > 0) thisPt.labelExtraY = newExtraY;
// make sure this label doesn't overlap any slices
if(!Array.isArray(trace.pull)) return; // this can only happen with array pulls
for(i = 0; i < wholeSide.length; i++) {
otherPt = wholeSide[i];
// overlap can only happen if the other point is pulled more than this one
if(otherPt === thisPt || (
(helpers.castOption(trace.pull, thisPt.pts) || 0) >=
(helpers.castOption(trace.pull, otherPt.pts) || 0))
) {
continue;
}
if((thisPt.pxmid[1] - otherPt.pxmid[1]) * yDiffSign > 0) {
// closer to the equator - by construction all of these happen first
// move the text vertically to get away from these slices
otherOuterY = otherPt.cyFinal + farthestY(otherPt.px0[1], otherPt.px1[1]);
newExtraY = otherOuterY - thisInnerY - thisPt.labelExtraY;
if(newExtraY * yDiffSign > 0) thisPt.labelExtraY += newExtraY;
} else if((thisOuterY + thisPt.labelExtraY - thisSliceOuterY) * yDiffSign > 0) {
// farther from the equator - happens after we've done all the
// vertical moving we're going to do
// move horizontally to get away from these more polar slices
// if we're moving horz. based on a slice that's several slices away from this one
// then we need some extra space for the lines to labels between them
xBuffer = 3 * xDiffSign * Math.abs(i - wholeSide.indexOf(thisPt));
otherOuterX = otherPt.cxFinal + farthestX(otherPt.px0[0], otherPt.px1[0]);
newExtraX = otherOuterX + xBuffer - (thisPt.cxFinal + thisPt.pxmid[0]) - thisPt.labelExtraX;
if(newExtraX * xDiffSign > 0) thisPt.labelExtraX += newExtraX;
}
}
}
for(yHalf = 0; yHalf < 2; yHalf++) {
equatorFirst = yHalf ? topFirst : bottomFirst;
farthestY = yHalf ? Math.max : Math.min;
yDiffSign = yHalf ? 1 : -1;
for(xHalf = 0; xHalf < 2; xHalf++) {
farthestX = xHalf ? Math.max : Math.min;
xDiffSign = xHalf ? 1 : -1;
// first sort the array
// note this is a copy of cd, so cd itself doesn't get sorted
// but we can still modify points in place.
thisQuad = quadrants[yHalf][xHalf];
thisQuad.sort(equatorFirst);
oppositeQuad = quadrants[1 - yHalf][xHalf];
wholeSide = oppositeQuad.concat(thisQuad);
thisQuadOutside = [];
for(i = 0; i < thisQuad.length; i++) {
if(thisQuad[i].yLabelMid !== undefined) thisQuadOutside.push(thisQuad[i]);
}
firstOppositeOutsidePt = false;
for(i = 0; yHalf && i < oppositeQuad.length; i++) {
if(oppositeQuad[i].yLabelMid !== undefined) {
firstOppositeOutsidePt = oppositeQuad[i];
break;
}
}
// each needs to avoid the previous
for(i = 0; i < thisQuadOutside.length; i++) {
var prevPt = i && thisQuadOutside[i - 1];
// bottom half needs to avoid the first label of the top half
// top half we still need to call scootOneLabel on the first slice
// so we can avoid other slices, but we don't pass a prevPt
if(firstOppositeOutsidePt && !i) prevPt = firstOppositeOutsidePt;
scootOneLabel(thisQuadOutside[i], prevPt);
}
}
}
}
function layoutAreas(cdModule, plotSize) {
var scaleGroups = [];
// figure out the center and maximum radius
for(var i = 0; i < cdModule.length; i++) {
var cd0 = cdModule[i][0];
var trace = cd0.trace;
var domain = trace.domain;
var width = plotSize.w * (domain.x[1] - domain.x[0]);
var height = plotSize.h * (domain.y[1] - domain.y[0]);
// leave some space for the title, if it will be displayed outside
if(trace.title.text && trace.title.position !== 'middle center') {
height -= getTitleSpace(cd0, plotSize);
}
var rx = width / 2;
var ry = height / 2;
if(trace.type === 'funnelarea' && !trace.scalegroup) {
ry /= trace.aspectratio;
}
cd0.r = Math.min(rx, ry) / (1 + getMaxPull(trace));
cd0.cx = plotSize.l + plotSize.w * (trace.domain.x[1] + trace.domain.x[0]) / 2;
cd0.cy = plotSize.t + plotSize.h * (1 - trace.domain.y[0]) - height / 2;
if(trace.title.text && trace.title.position.indexOf('bottom') !== -1) {
cd0.cy -= getTitleSpace(cd0, plotSize);
}
if(trace.scalegroup && scaleGroups.indexOf(trace.scalegroup) === -1) {
scaleGroups.push(trace.scalegroup);
}
}
groupScale(cdModule, scaleGroups);
}
function groupScale(cdModule, scaleGroups) {
var cd0, i, trace;
// scale those that are grouped
for(var k = 0; k < scaleGroups.length; k++) {
var min = Infinity;
var g = scaleGroups[k];
for(i = 0; i < cdModule.length; i++) {
cd0 = cdModule[i][0];
trace = cd0.trace;
if(trace.scalegroup === g) {
var area;
if(trace.type === 'pie') {
area = cd0.r * cd0.r;
} else if(trace.type === 'funnelarea') {
var rx, ry;
if(trace.aspectratio > 1) {
rx = cd0.r;
ry = rx / trace.aspectratio;
} else {
ry = cd0.r;
rx = ry * trace.aspectratio;
}
rx *= (1 + trace.baseratio) / 2;
area = rx * ry;
}
min = Math.min(min, area / cd0.vTotal);
}
}
for(i = 0; i < cdModule.length; i++) {
cd0 = cdModule[i][0];
trace = cd0.trace;
if(trace.scalegroup === g) {
var v = min * cd0.vTotal;
if(trace.type === 'funnelarea') {
v /= (1 + trace.baseratio) / 2;
v /= trace.aspectratio;
}
cd0.r = Math.sqrt(v);
}
}
}
}
function setCoords(cd) {
var cd0 = cd[0];
var r = cd0.r;
var trace = cd0.trace;
var currentAngle = helpers.getRotationAngle(trace.rotation);
var angleFactor = 2 * Math.PI / cd0.vTotal;
var firstPt = 'px0';
var lastPt = 'px1';
var i, cdi, currentCoords;
if(trace.direction === 'counterclockwise') {
for(i = 0; i < cd.length; i++) {
if(!cd[i].hidden) break; // find the first non-hidden slice
}
if(i === cd.length) return; // all slices hidden
currentAngle += angleFactor * cd[i].v;
angleFactor *= -1;
firstPt = 'px1';
lastPt = 'px0';
}
currentCoords = getCoords(r, currentAngle);
for(i = 0; i < cd.length; i++) {
cdi = cd[i];
if(cdi.hidden) continue;
cdi[firstPt] = currentCoords;
cdi.startangle = currentAngle;
currentAngle += angleFactor * cdi.v / 2;
cdi.pxmid = getCoords(r, currentAngle);
cdi.midangle = currentAngle;
currentAngle += angleFactor * cdi.v / 2;
currentCoords = getCoords(r, currentAngle);
cdi.stopangle = currentAngle;
cdi[lastPt] = currentCoords;
cdi.largeArc = (cdi.v > cd0.vTotal / 2) ? 1 : 0;
cdi.halfangle = Math.PI * Math.min(cdi.v / cd0.vTotal, 0.5);
cdi.ring = 1 - trace.hole;
cdi.rInscribed = getInscribedRadiusFraction(cdi, cd0);
}
}
function getCoords(r, angle) {
return [r * Math.sin(angle), -r * Math.cos(angle)];
}
function formatSliceLabel(gd, pt, cd0) {
var fullLayout = gd._fullLayout;
var trace = cd0.trace;
// look for textemplate
var texttemplate = trace.texttemplate;
// now insert text
var textinfo = trace.textinfo;
if(!texttemplate && textinfo && textinfo !== 'none') {
var parts = textinfo.split('+');
var hasFlag = function(flag) { return parts.indexOf(flag) !== -1; };
var hasLabel = hasFlag('label');
var hasText = hasFlag('text');
var hasValue = hasFlag('value');
var hasPercent = hasFlag('percent');
var separators = fullLayout.separators;
var text;
text = hasLabel ? [pt.label] : [];
if(hasText) {
var tx = helpers.getFirstFilled(trace.text, pt.pts);
if(isValidTextValue(tx)) text.push(tx);
}
if(hasValue) text.push(helpers.formatPieValue(pt.v, separators));
if(hasPercent) text.push(helpers.formatPiePercent(pt.v / cd0.vTotal, separators));
pt.text = text.join('
');
}
function makeTemplateVariables(pt) {
return {
label: pt.label,
value: pt.v,
valueLabel: helpers.formatPieValue(pt.v, fullLayout.separators),
percent: pt.v / cd0.vTotal,
percentLabel: helpers.formatPiePercent(pt.v / cd0.vTotal, fullLayout.separators),
color: pt.color,
text: pt.text,
customdata: Lib.castOption(trace, pt.i, 'customdata')
};
}
if(texttemplate) {
var txt = Lib.castOption(trace, pt.i, 'texttemplate');
if(!txt) {
pt.text = '';
} else {
var obj = makeTemplateVariables(pt);
var ptTx = helpers.getFirstFilled(trace.text, pt.pts);
if(isValidTextValue(ptTx) || ptTx === '') obj.text = ptTx;
pt.text = Lib.texttemplateString(txt, obj, gd._fullLayout._d3locale, obj, trace._meta || {});
}
}
}
function computeTransform(
transform, // inout
textBB // in
) {
var a = transform.rotate * Math.PI / 180;
var cosA = Math.cos(a);
var sinA = Math.sin(a);
var midX = (textBB.left + textBB.right) / 2;
var midY = (textBB.top + textBB.bottom) / 2;
transform.textX = midX * cosA - midY * sinA;
transform.textY = midX * sinA + midY * cosA;
transform.noCenter = true;
}
module.exports = {
plot: plot,
formatSliceLabel: formatSliceLabel,
transformInsideText: transformInsideText,
determineInsideTextFont: determineInsideTextFont,
positionTitleOutside: positionTitleOutside,
prerenderTitles: prerenderTitles,
layoutAreas: layoutAreas,
attachFxHandlers: attachFxHandlers,
computeTransform: computeTransform
};
},{"../../components/color":366,"../../components/drawing":388,"../../components/fx":406,"../../lib":503,"../../lib/svg_text_utils":529,"../../plots/plots":619,"../bar/constants":650,"../bar/uniform_text":664,"./event_data":903,"./helpers":904,"@plotly/d3":58}],909:[function(_dereq_,module,exports){
'use strict';
var d3 = _dereq_('@plotly/d3');
var styleOne = _dereq_('./style_one');
var resizeText = _dereq_('../bar/uniform_text').resizeText;
module.exports = function style(gd) {
var s = gd._fullLayout._pielayer.selectAll('.trace');
resizeText(gd, s, 'pie');
s.each(function(cd) {
var cd0 = cd[0];
var trace = cd0.trace;
var traceSelection = d3.select(this);
traceSelection.style({opacity: trace.opacity});
traceSelection.selectAll('path.surface').each(function(pt) {
d3.select(this).call(styleOne, pt, trace);
});
});
};
},{"../bar/uniform_text":664,"./style_one":910,"@plotly/d3":58}],910:[function(_dereq_,module,exports){
'use strict';
var Color = _dereq_('../../components/color');
var castOption = _dereq_('./helpers').castOption;
module.exports = function styleOne(s, pt, trace) {
var line = trace.marker.line;
var lineColor = castOption(line.color, pt.pts) || Color.defaultLine;
var lineWidth = castOption(line.width, pt.pts) || 0;
s.style('stroke-width', lineWidth)
.call(Color.fill, pt.color)
.call(Color.stroke, lineColor);
};
},{"../../components/color":366,"./helpers":904}],911:[function(_dereq_,module,exports){
'use strict';
var scatterglAttrs = _dereq_('../scatter/attributes');
module.exports = {
x: scatterglAttrs.x,
y: scatterglAttrs.y,
xy: {
valType: 'data_array',
editType: 'calc',
},
indices: {
valType: 'data_array',
editType: 'calc',
},
xbounds: {
valType: 'data_array',
editType: 'calc',
},
ybounds: {
valType: 'data_array',
editType: 'calc',
},
text: scatterglAttrs.text,
marker: {
color: {
valType: 'color',
arrayOk: false,
editType: 'calc',
},
opacity: {
valType: 'number',
min: 0,
max: 1,
dflt: 1,
arrayOk: false,
editType: 'calc',
},
blend: {
valType: 'boolean',
dflt: null,
editType: 'calc',
},
sizemin: {
valType: 'number',
min: 0.1,
max: 2,
dflt: 0.5,
editType: 'calc',
},
sizemax: {
valType: 'number',
min: 0.1,
dflt: 20,
editType: 'calc',
},
border: {
color: {
valType: 'color',
arrayOk: false,
editType: 'calc',
},
arearatio: {
valType: 'number',
min: 0,
max: 1,
dflt: 0,
editType: 'calc',
},
editType: 'calc'
},
editType: 'calc'
},
transforms: undefined
};
},{"../scatter/attributes":925}],912:[function(_dereq_,module,exports){
'use strict';
var createPointCloudRenderer = _dereq_('../../../stackgl_modules').gl_pointcloud2d;
var str2RGBArray = _dereq_('../../lib/str2rgbarray');
var findExtremes = _dereq_('../../plots/cartesian/autorange').findExtremes;
var getTraceColor = _dereq_('../scatter/get_trace_color');
function Pointcloud(scene, uid) {
this.scene = scene;
this.uid = uid;
this.type = 'pointcloud';
this.pickXData = [];
this.pickYData = [];
this.xData = [];
this.yData = [];
this.textLabels = [];
this.color = 'rgb(0, 0, 0)';
this.name = '';
this.hoverinfo = 'all';
this.idToIndex = new Int32Array(0);
this.bounds = [0, 0, 0, 0];
this.pointcloudOptions = {
positions: new Float32Array(0),
idToIndex: this.idToIndex,
sizemin: 0.5,
sizemax: 12,
color: [0, 0, 0, 1],
areaRatio: 1,
borderColor: [0, 0, 0, 1]
};
this.pointcloud = createPointCloudRenderer(scene.glplot, this.pointcloudOptions);
this.pointcloud._trace = this; // scene2d requires this prop
}
var proto = Pointcloud.prototype;
proto.handlePick = function(pickResult) {
var index = this.idToIndex[pickResult.pointId];
// prefer the readout from XY, if present
return {
trace: this,
dataCoord: pickResult.dataCoord,
traceCoord: this.pickXYData ?
[this.pickXYData[index * 2], this.pickXYData[index * 2 + 1]] :
[this.pickXData[index], this.pickYData[index]],
textLabel: Array.isArray(this.textLabels) ?
this.textLabels[index] :
this.textLabels,
color: this.color,
name: this.name,
pointIndex: index,
hoverinfo: this.hoverinfo
};
};
proto.update = function(options) {
this.index = options.index;
this.textLabels = options.text;
this.name = options.name;
this.hoverinfo = options.hoverinfo;
this.bounds = [Infinity, Infinity, -Infinity, -Infinity];
this.updateFast(options);
this.color = getTraceColor(options, {});
};
proto.updateFast = function(options) {
var x = this.xData = this.pickXData = options.x;
var y = this.yData = this.pickYData = options.y;
var xy = this.pickXYData = options.xy;
var userBounds = options.xbounds && options.ybounds;
var index = options.indices;
var len;
var idToIndex;
var positions;
var bounds = this.bounds;
var xx, yy, i;
if(xy) {
positions = xy;
// dividing xy.length by 2 and truncating to integer if xy.length was not even
len = xy.length >>> 1;
if(userBounds) {
bounds[0] = options.xbounds[0];
bounds[2] = options.xbounds[1];
bounds[1] = options.ybounds[0];
bounds[3] = options.ybounds[1];
} else {
for(i = 0; i < len; i++) {
xx = positions[i * 2];
yy = positions[i * 2 + 1];
if(xx < bounds[0]) bounds[0] = xx;
if(xx > bounds[2]) bounds[2] = xx;
if(yy < bounds[1]) bounds[1] = yy;
if(yy > bounds[3]) bounds[3] = yy;
}
}
if(index) {
idToIndex = index;
} else {
idToIndex = new Int32Array(len);
for(i = 0; i < len; i++) {
idToIndex[i] = i;
}
}
} else {
len = x.length;
positions = new Float32Array(2 * len);
idToIndex = new Int32Array(len);
for(i = 0; i < len; i++) {
xx = x[i];
yy = y[i];
idToIndex[i] = i;
positions[i * 2] = xx;
positions[i * 2 + 1] = yy;
if(xx < bounds[0]) bounds[0] = xx;
if(xx > bounds[2]) bounds[2] = xx;
if(yy < bounds[1]) bounds[1] = yy;
if(yy > bounds[3]) bounds[3] = yy;
}
}
this.idToIndex = idToIndex;
this.pointcloudOptions.idToIndex = idToIndex;
this.pointcloudOptions.positions = positions;
var markerColor = str2RGBArray(options.marker.color);
var borderColor = str2RGBArray(options.marker.border.color);
var opacity = options.opacity * options.marker.opacity;
markerColor[3] *= opacity;
this.pointcloudOptions.color = markerColor;
// detect blending from the number of points, if undefined
// because large data with blending hits performance
var blend = options.marker.blend;
if(blend === null) {
var maxPoints = 100;
blend = x.length < maxPoints || y.length < maxPoints;
}
this.pointcloudOptions.blend = blend;
borderColor[3] *= opacity;
this.pointcloudOptions.borderColor = borderColor;
var markerSizeMin = options.marker.sizemin;
var markerSizeMax = Math.max(options.marker.sizemax, options.marker.sizemin);
this.pointcloudOptions.sizeMin = markerSizeMin;
this.pointcloudOptions.sizeMax = markerSizeMax;
this.pointcloudOptions.areaRatio = options.marker.border.arearatio;
this.pointcloud.update(this.pointcloudOptions);
// add item for autorange routine
var xa = this.scene.xaxis;
var ya = this.scene.yaxis;
var pad = markerSizeMax / 2 || 0.5;
options._extremes[xa._id] = findExtremes(xa, [bounds[0], bounds[2]], {ppad: pad});
options._extremes[ya._id] = findExtremes(ya, [bounds[1], bounds[3]], {ppad: pad});
};
proto.dispose = function() {
this.pointcloud.dispose();
};
function createPointcloud(scene, data) {
var plot = new Pointcloud(scene, data.uid);
plot.update(data);
return plot;
}
module.exports = createPointcloud;
},{"../../../stackgl_modules":1119,"../../lib/str2rgbarray":528,"../../plots/cartesian/autorange":553,"../scatter/get_trace_color":935}],913:[function(_dereq_,module,exports){
'use strict';
var Lib = _dereq_('../../lib');
var attributes = _dereq_('./attributes');
module.exports = function supplyDefaults(traceIn, traceOut, defaultColor) {
function coerce(attr, dflt) {
return Lib.coerce(traceIn, traceOut, attributes, attr, dflt);
}
coerce('x');
coerce('y');
coerce('xbounds');
coerce('ybounds');
if(traceIn.xy && traceIn.xy instanceof Float32Array) {
traceOut.xy = traceIn.xy;
}
if(traceIn.indices && traceIn.indices instanceof Int32Array) {
traceOut.indices = traceIn.indices;
}
coerce('text');
coerce('marker.color', defaultColor);
coerce('marker.opacity');
coerce('marker.blend');
coerce('marker.sizemin');
coerce('marker.sizemax');
coerce('marker.border.color', defaultColor);
coerce('marker.border.arearatio');
// disable 1D transforms - that would defeat the purpose of this trace type, performance!
traceOut._length = null;
};
},{"../../lib":503,"./attributes":911}],914:[function(_dereq_,module,exports){
'use strict';
var deprecationWarning = [
'*pointcloud* trace is deprecated!',
'Please consider switching to the *scattergl* trace type.'
].join(' ');
module.exports = {
attributes: _dereq_('./attributes'),
supplyDefaults: _dereq_('./defaults'),
// reuse the Scatter3D 'dummy' calc step so that legends know what to do
calc: _dereq_('../scatter3d/calc'),
plot: _dereq_('./convert'),
moduleType: 'trace',
name: 'pointcloud',
basePlotModule: _dereq_('../../plots/gl2d'),
categories: ['gl', 'gl2d', 'showLegend'],
meta: {
}
};
},{"../../plots/gl2d":596,"../scatter3d/calc":954,"./attributes":911,"./convert":912,"./defaults":913}],915:[function(_dereq_,module,exports){
'use strict';
var fontAttrs = _dereq_('../../plots/font_attributes');
var baseAttrs = _dereq_('../../plots/attributes');
var colorAttrs = _dereq_('../../components/color/attributes');
var fxAttrs = _dereq_('../../components/fx/attributes');
var domainAttrs = _dereq_('../../plots/domain').attributes;
var hovertemplateAttrs = _dereq_('../../plots/template_attributes').hovertemplateAttrs;
var colorAttributes = _dereq_('../../components/colorscale/attributes');
var templatedArray = _dereq_('../../plot_api/plot_template').templatedArray;
var descriptionOnlyNumbers = _dereq_('../../plots/cartesian/axis_format_attributes').descriptionOnlyNumbers;
var extendFlat = _dereq_('../../lib/extend').extendFlat;
var overrideAll = _dereq_('../../plot_api/edit_types').overrideAll;
var attrs = module.exports = overrideAll({
hoverinfo: extendFlat({}, baseAttrs.hoverinfo, {
flags: [],
arrayOk: false,
}),
hoverlabel: fxAttrs.hoverlabel,
domain: domainAttrs({name: 'sankey', trace: true}),
orientation: {
valType: 'enumerated',
values: ['v', 'h'],
dflt: 'h',
},
valueformat: {
valType: 'string',
dflt: '.3s',
description: descriptionOnlyNumbers('value')
},
valuesuffix: {
valType: 'string',
dflt: '',
},
arrangement: {
valType: 'enumerated',
values: ['snap', 'perpendicular', 'freeform', 'fixed'],
dflt: 'snap',
},
textfont: fontAttrs({
}),
// Remove top-level customdata
customdata: undefined,
node: {
label: {
valType: 'data_array',
dflt: [],
},
groups: {
valType: 'info_array',
impliedEdits: {'x': [], 'y': []},
dimensions: 2,
freeLength: true,
dflt: [],
items: {valType: 'number', editType: 'calc'},
},
x: {
valType: 'data_array',
dflt: [],
},
y: {
valType: 'data_array',
dflt: [],
},
color: {
valType: 'color',
arrayOk: true,
},
customdata: {
valType: 'data_array',
editType: 'calc',
},
line: {
color: {
valType: 'color',
dflt: colorAttrs.defaultLine,
arrayOk: true,
},
width: {
valType: 'number',
min: 0,
dflt: 0.5,
arrayOk: true,
}
},
pad: {
valType: 'number',
arrayOk: false,
min: 0,
dflt: 20,
},
thickness: {
valType: 'number',
arrayOk: false,
min: 1,
dflt: 20,
},
hoverinfo: {
valType: 'enumerated',
values: ['all', 'none', 'skip'],
dflt: 'all',
},
hoverlabel: fxAttrs.hoverlabel, // needs editType override,
hovertemplate: hovertemplateAttrs({}, {
keys: ['value', 'label']
}),
},
link: {
label: {
valType: 'data_array',
dflt: [],
},
color: {
valType: 'color',
arrayOk: true,
},
customdata: {
valType: 'data_array',
editType: 'calc',
},
line: {
color: {
valType: 'color',
dflt: colorAttrs.defaultLine,
arrayOk: true,
},
width: {
valType: 'number',
min: 0,
dflt: 0,
arrayOk: true,
}
},
source: {
valType: 'data_array',
dflt: [],
},
target: {
valType: 'data_array',
dflt: [],
},
value: {
valType: 'data_array',
dflt: [],
},
hoverinfo: {
valType: 'enumerated',
values: ['all', 'none', 'skip'],
dflt: 'all',
},
hoverlabel: fxAttrs.hoverlabel, // needs editType override,
hovertemplate: hovertemplateAttrs({}, {
keys: ['value', 'label']
}),
colorscales: templatedArray('concentrationscales', {
editType: 'calc',
label: {
valType: 'string',
editType: 'calc',
dflt: ''
},
cmax: {
valType: 'number',
editType: 'calc',
dflt: 1,
},
cmin: {
valType: 'number',
editType: 'calc',
dflt: 0,
},
colorscale: extendFlat(colorAttributes().colorscale, {dflt: [[0, 'white'], [1, 'black']]})
}),
}
}, 'calc', 'nested');
attrs.transforms = undefined;
},{"../../components/color/attributes":365,"../../components/colorscale/attributes":373,"../../components/fx/attributes":397,"../../lib/extend":493,"../../plot_api/edit_types":536,"../../plot_api/plot_template":543,"../../plots/attributes":550,"../../plots/cartesian/axis_format_attributes":557,"../../plots/domain":584,"../../plots/font_attributes":585,"../../plots/template_attributes":633}],916:[function(_dereq_,module,exports){
'use strict';
var overrideAll = _dereq_('../../plot_api/edit_types').overrideAll;
var getModuleCalcData = _dereq_('../../plots/get_data').getModuleCalcData;
var plot = _dereq_('./plot');
var fxAttrs = _dereq_('../../components/fx/layout_attributes');
var setCursor = _dereq_('../../lib/setcursor');
var dragElement = _dereq_('../../components/dragelement');
var prepSelect = _dereq_('../../plots/cartesian/select').prepSelect;
var Lib = _dereq_('../../lib');
var Registry = _dereq_('../../registry');
var SANKEY = 'sankey';
exports.name = SANKEY;
exports.baseLayoutAttrOverrides = overrideAll({
hoverlabel: fxAttrs.hoverlabel
}, 'plot', 'nested');
exports.plot = function(gd) {
var calcData = getModuleCalcData(gd.calcdata, SANKEY)[0];
plot(gd, calcData);
exports.updateFx(gd);
};
exports.clean = function(newFullData, newFullLayout, oldFullData, oldFullLayout) {
var hadPlot = (oldFullLayout._has && oldFullLayout._has(SANKEY));
var hasPlot = (newFullLayout._has && newFullLayout._has(SANKEY));
if(hadPlot && !hasPlot) {
oldFullLayout._paperdiv.selectAll('.sankey').remove();
oldFullLayout._paperdiv.selectAll('.bgsankey').remove();
}
};
exports.updateFx = function(gd) {
for(var i = 0; i < gd._fullData.length; i++) {
subplotUpdateFx(gd, i);
}
};
function subplotUpdateFx(gd, index) {
var trace = gd._fullData[index];
var fullLayout = gd._fullLayout;
var dragMode = fullLayout.dragmode;
var cursor = fullLayout.dragmode === 'pan' ? 'move' : 'crosshair';
var bgRect = trace._bgRect;
if(dragMode === 'pan' || dragMode === 'zoom') return;
setCursor(bgRect, cursor);
var xaxis = {
_id: 'x',
c2p: Lib.identity,
_offset: trace._sankey.translateX,
_length: trace._sankey.width
};
var yaxis = {
_id: 'y',
c2p: Lib.identity,
_offset: trace._sankey.translateY,
_length: trace._sankey.height
};
// Note: dragOptions is needed to be declared for all dragmodes because
// it's the object that holds persistent selection state.
var dragOptions = {
gd: gd,
element: bgRect.node(),
plotinfo: {
id: index,
xaxis: xaxis,
yaxis: yaxis,
fillRangeItems: Lib.noop
},
subplot: index,
// create mock x/y axes for hover routine
xaxes: [xaxis],
yaxes: [yaxis],
doneFnCompleted: function(selection) {
var traceNow = gd._fullData[index];
var newGroups;
var oldGroups = traceNow.node.groups.slice();
var newGroup = [];
function findNode(pt) {
var nodes = traceNow._sankey.graph.nodes;
for(var i = 0; i < nodes.length; i++) {
if(nodes[i].pointNumber === pt) return nodes[i];
}
}
for(var j = 0; j < selection.length; j++) {
var node = findNode(selection[j].pointNumber);
if(!node) continue;
// If the node represents a group
if(node.group) {
// Add all its children to the current selection
for(var k = 0; k < node.childrenNodes.length; k++) {
newGroup.push(node.childrenNodes[k].pointNumber);
}
// Flag group for removal from existing list of groups
oldGroups[node.pointNumber - traceNow.node._count] = false;
} else {
newGroup.push(node.pointNumber);
}
}
newGroups = oldGroups
.filter(Boolean)
.concat([newGroup]);
Registry.call('_guiRestyle', gd, {
'node.groups': [ newGroups ]
}, index);
}
};
dragOptions.prepFn = function(e, startX, startY) {
prepSelect(e, startX, startY, dragOptions, dragMode);
};
dragElement.init(dragOptions);
}
},{"../../components/dragelement":385,"../../components/fx/layout_attributes":407,"../../lib":503,"../../lib/setcursor":524,"../../plot_api/edit_types":536,"../../plots/cartesian/select":575,"../../plots/get_data":593,"../../registry":638,"./plot":921}],917:[function(_dereq_,module,exports){
'use strict';
var tarjan = _dereq_('strongly-connected-components');
var Lib = _dereq_('../../lib');
var wrap = _dereq_('../../lib/gup').wrap;
var isArrayOrTypedArray = Lib.isArrayOrTypedArray;
var isIndex = Lib.isIndex;
var Colorscale = _dereq_('../../components/colorscale');
function convertToD3Sankey(trace) {
var nodeSpec = trace.node;
var linkSpec = trace.link;
var links = [];
var hasLinkColorArray = isArrayOrTypedArray(linkSpec.color);
var hasLinkCustomdataArray = isArrayOrTypedArray(linkSpec.customdata);
var linkedNodes = {};
var components = {};
var componentCount = linkSpec.colorscales.length;
var i;
for(i = 0; i < componentCount; i++) {
var cscale = linkSpec.colorscales[i];
var specs = Colorscale.extractScale(cscale, {cLetter: 'c'});
var scale = Colorscale.makeColorScaleFunc(specs);
components[cscale.label] = scale;
}
var maxNodeId = 0;
for(i = 0; i < linkSpec.value.length; i++) {
if(linkSpec.source[i] > maxNodeId) maxNodeId = linkSpec.source[i];
if(linkSpec.target[i] > maxNodeId) maxNodeId = linkSpec.target[i];
}
var nodeCount = maxNodeId + 1;
trace.node._count = nodeCount;
// Group nodes
var j;
var groups = trace.node.groups;
var groupLookup = {};
for(i = 0; i < groups.length; i++) {
var group = groups[i];
// Build a lookup table to quickly find in which group a node is
for(j = 0; j < group.length; j++) {
var nodeIndex = group[j];
var groupIndex = nodeCount + i;
if(groupLookup.hasOwnProperty(nodeIndex)) {
Lib.warn('Node ' + nodeIndex + ' is already part of a group.');
} else {
groupLookup[nodeIndex] = groupIndex;
}
}
}
// Process links
var groupedLinks = {
source: [],
target: []
};
for(i = 0; i < linkSpec.value.length; i++) {
var val = linkSpec.value[i];
// remove negative values, but keep zeros with special treatment
var source = linkSpec.source[i];
var target = linkSpec.target[i];
if(!(val > 0 && isIndex(source, nodeCount) && isIndex(target, nodeCount))) {
continue;
}
// Remove links that are within the same group
if(groupLookup.hasOwnProperty(source) && groupLookup.hasOwnProperty(target) && groupLookup[source] === groupLookup[target]) {
continue;
}
// if link targets a node in the group, relink target to that group
if(groupLookup.hasOwnProperty(target)) {
target = groupLookup[target];
}
// if link originates from a node in a group, relink source to that group
if(groupLookup.hasOwnProperty(source)) {
source = groupLookup[source];
}
source = +source;
target = +target;
linkedNodes[source] = linkedNodes[target] = true;
var label = '';
if(linkSpec.label && linkSpec.label[i]) label = linkSpec.label[i];
var concentrationscale = null;
if(label && components.hasOwnProperty(label)) concentrationscale = components[label];
links.push({
pointNumber: i,
label: label,
color: hasLinkColorArray ? linkSpec.color[i] : linkSpec.color,
customdata: hasLinkCustomdataArray ? linkSpec.customdata[i] : linkSpec.customdata,
concentrationscale: concentrationscale,
source: source,
target: target,
value: +val
});
groupedLinks.source.push(source);
groupedLinks.target.push(target);
}
// Process nodes
var totalCount = nodeCount + groups.length;
var hasNodeColorArray = isArrayOrTypedArray(nodeSpec.color);
var hasNodeCustomdataArray = isArrayOrTypedArray(nodeSpec.customdata);
var nodes = [];
for(i = 0; i < totalCount; i++) {
if(!linkedNodes[i]) continue;
var l = nodeSpec.label[i];
nodes.push({
group: (i > nodeCount - 1),
childrenNodes: [],
pointNumber: i,
label: l,
color: hasNodeColorArray ? nodeSpec.color[i] : nodeSpec.color,
customdata: hasNodeCustomdataArray ? nodeSpec.customdata[i] : nodeSpec.customdata
});
}
// Check if we have circularity on the resulting graph
var circular = false;
if(circularityPresent(totalCount, groupedLinks.source, groupedLinks.target)) {
circular = true;
}
return {
circular: circular,
links: links,
nodes: nodes,
// Data structure for groups
groups: groups,
groupLookup: groupLookup
};
}
function circularityPresent(nodeLen, sources, targets) {
var nodes = Lib.init2dArray(nodeLen, 0);
for(var i = 0; i < Math.min(sources.length, targets.length); i++) {
if(Lib.isIndex(sources[i], nodeLen) && Lib.isIndex(targets[i], nodeLen)) {
if(sources[i] === targets[i]) {
return true; // self-link which is also a scc of one
}
nodes[sources[i]].push(targets[i]);
}
}
var scc = tarjan(nodes);
// Tarján's strongly connected components algorithm coded by Mikola Lysenko
// returns at least one non-singular component if there's circularity in the graph
return scc.components.some(function(c) {
return c.length > 1;
});
}
module.exports = function calc(gd, trace) {
var result = convertToD3Sankey(trace);
return wrap({
circular: result.circular,
_nodes: result.nodes,
_links: result.links,
// Data structure for grouping
_groups: result.groups,
_groupLookup: result.groupLookup,
});
};
},{"../../components/colorscale":378,"../../lib":503,"../../lib/gup":500,"strongly-connected-components":306}],918:[function(_dereq_,module,exports){
'use strict';
module.exports = {
nodeTextOffsetHorizontal: 4,
nodeTextOffsetVertical: 3,
nodePadAcross: 10,
sankeyIterations: 50,
forceIterations: 5,
forceTicksPerFrame: 10,
duration: 500,
ease: 'linear',
cn: {
sankey: 'sankey',
sankeyLinks: 'sankey-links',
sankeyLink: 'sankey-link',
sankeyNodeSet: 'sankey-node-set',
sankeyNode: 'sankey-node',
nodeRect: 'node-rect',
nodeLabel: 'node-label'
}
};
},{}],919:[function(_dereq_,module,exports){
'use strict';
var Lib = _dereq_('../../lib');
var attributes = _dereq_('./attributes');
var Color = _dereq_('../../components/color');
var tinycolor = _dereq_('tinycolor2');
var handleDomainDefaults = _dereq_('../../plots/domain').defaults;
var handleHoverLabelDefaults = _dereq_('../../components/fx/hoverlabel_defaults');
var Template = _dereq_('../../plot_api/plot_template');
var handleArrayContainerDefaults = _dereq_('../../plots/array_container_defaults');
module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout) {
function coerce(attr, dflt) {
return Lib.coerce(traceIn, traceOut, attributes, attr, dflt);
}
var hoverlabelDefault = Lib.extendDeep(layout.hoverlabel, traceIn.hoverlabel);
// node attributes
var nodeIn = traceIn.node;
var nodeOut = Template.newContainer(traceOut, 'node');
function coerceNode(attr, dflt) {
return Lib.coerce(nodeIn, nodeOut, attributes.node, attr, dflt);
}
coerceNode('label');
coerceNode('groups');
coerceNode('x');
coerceNode('y');
coerceNode('pad');
coerceNode('thickness');
coerceNode('line.color');
coerceNode('line.width');
coerceNode('hoverinfo', traceIn.hoverinfo);
handleHoverLabelDefaults(nodeIn, nodeOut, coerceNode, hoverlabelDefault);
coerceNode('hovertemplate');
var colors = layout.colorway;
var defaultNodePalette = function(i) {return colors[i % colors.length];};
coerceNode('color', nodeOut.label.map(function(d, i) {
return Color.addOpacity(defaultNodePalette(i), 0.8);
}));
coerceNode('customdata');
// link attributes
var linkIn = traceIn.link || {};
var linkOut = Template.newContainer(traceOut, 'link');
function coerceLink(attr, dflt) {
return Lib.coerce(linkIn, linkOut, attributes.link, attr, dflt);
}
coerceLink('label');
coerceLink('source');
coerceLink('target');
coerceLink('value');
coerceLink('line.color');
coerceLink('line.width');
coerceLink('hoverinfo', traceIn.hoverinfo);
handleHoverLabelDefaults(linkIn, linkOut, coerceLink, hoverlabelDefault);
coerceLink('hovertemplate');
var defaultLinkColor = tinycolor(layout.paper_bgcolor).getLuminance() < 0.333 ?
'rgba(255, 255, 255, 0.6)' :
'rgba(0, 0, 0, 0.2)';
coerceLink('color', Lib.repeat(defaultLinkColor, linkOut.value.length));
coerceLink('customdata');
handleArrayContainerDefaults(linkIn, linkOut, {
name: 'colorscales',
handleItemDefaults: concentrationscalesDefaults
});
handleDomainDefaults(traceOut, layout, coerce);
coerce('orientation');
coerce('valueformat');
coerce('valuesuffix');
var dfltArrangement;
if(nodeOut.x.length && nodeOut.y.length) {
dfltArrangement = 'freeform';
}
coerce('arrangement', dfltArrangement);
Lib.coerceFont(coerce, 'textfont', Lib.extendFlat({}, layout.font));
// disable 1D transforms - arrays here are 1D but their lengths/meanings
// don't match, between nodes and links
traceOut._length = null;
};
function concentrationscalesDefaults(In, Out) {
function coerce(attr, dflt) {
return Lib.coerce(In, Out, attributes.link.colorscales, attr, dflt);
}
coerce('label');
coerce('cmin');
coerce('cmax');
coerce('colorscale');
}
},{"../../components/color":366,"../../components/fx/hoverlabel_defaults":404,"../../lib":503,"../../plot_api/plot_template":543,"../../plots/array_container_defaults":549,"../../plots/domain":584,"./attributes":915,"tinycolor2":312}],920:[function(_dereq_,module,exports){
'use strict';
module.exports = {
attributes: _dereq_('./attributes'),
supplyDefaults: _dereq_('./defaults'),
calc: _dereq_('./calc'),
plot: _dereq_('./plot'),
moduleType: 'trace',
name: 'sankey',
basePlotModule: _dereq_('./base_plot'),
selectPoints: _dereq_('./select.js'),
categories: ['noOpacity'],
meta: {
}
};
},{"./attributes":915,"./base_plot":916,"./calc":917,"./defaults":919,"./plot":921,"./select.js":923}],921:[function(_dereq_,module,exports){
'use strict';
var d3 = _dereq_('@plotly/d3');
var Lib = _dereq_('../../lib');
var numberFormat = Lib.numberFormat;
var render = _dereq_('./render');
var Fx = _dereq_('../../components/fx');
var Color = _dereq_('../../components/color');
var cn = _dereq_('./constants').cn;
var _ = Lib._;
function renderableValuePresent(d) {return d !== '';}
function ownTrace(selection, d) {
return selection.filter(function(s) {return s.key === d.traceId;});
}
function makeTranslucent(element, alpha) {
d3.select(element)
.select('path')
.style('fill-opacity', alpha);
d3.select(element)
.select('rect')
.style('fill-opacity', alpha);
}
function makeTextContrasty(element) {
d3.select(element)
.select('text.name')
.style('fill', 'black');
}
function relatedLinks(d) {
return function(l) {
return d.node.sourceLinks.indexOf(l.link) !== -1 || d.node.targetLinks.indexOf(l.link) !== -1;
};
}
function relatedNodes(l) {
return function(d) {
return d.node.sourceLinks.indexOf(l.link) !== -1 || d.node.targetLinks.indexOf(l.link) !== -1;
};
}
function nodeHoveredStyle(sankeyNode, d, sankey) {
if(d && sankey) {
ownTrace(sankey, d)
.selectAll('.' + cn.sankeyLink)
.filter(relatedLinks(d))
.call(linkHoveredStyle.bind(0, d, sankey, false));
}
}
function nodeNonHoveredStyle(sankeyNode, d, sankey) {
if(d && sankey) {
ownTrace(sankey, d)
.selectAll('.' + cn.sankeyLink)
.filter(relatedLinks(d))
.call(linkNonHoveredStyle.bind(0, d, sankey, false));
}
}
function linkHoveredStyle(d, sankey, visitNodes, sankeyLink) {
var label = sankeyLink.datum().link.label;
sankeyLink.style('fill-opacity', function(l) {
if(!l.link.concentrationscale) {
return 0.4;
}
});
if(label) {
ownTrace(sankey, d)
.selectAll('.' + cn.sankeyLink)
.filter(function(l) {return l.link.label === label;})
.style('fill-opacity', function(l) {
if(!l.link.concentrationscale) {
return 0.4;
}
});
}
if(visitNodes) {
ownTrace(sankey, d)
.selectAll('.' + cn.sankeyNode)
.filter(relatedNodes(d))
.call(nodeHoveredStyle);
}
}
function linkNonHoveredStyle(d, sankey, visitNodes, sankeyLink) {
var label = sankeyLink.datum().link.label;
sankeyLink.style('fill-opacity', function(d) {return d.tinyColorAlpha;});
if(label) {
ownTrace(sankey, d)
.selectAll('.' + cn.sankeyLink)
.filter(function(l) {return l.link.label === label;})
.style('fill-opacity', function(d) {return d.tinyColorAlpha;});
}
if(visitNodes) {
ownTrace(sankey, d)
.selectAll(cn.sankeyNode)
.filter(relatedNodes(d))
.call(nodeNonHoveredStyle);
}
}
// does not support array values for now
function castHoverOption(trace, attr) {
var labelOpts = trace.hoverlabel || {};
var val = Lib.nestedProperty(labelOpts, attr).get();
return Array.isArray(val) ? false : val;
}
module.exports = function plot(gd, calcData) {
var fullLayout = gd._fullLayout;
var svg = fullLayout._paper;
var size = fullLayout._size;
// stash initial view
for(var i = 0; i < gd._fullData.length; i++) {
if(!gd._fullData[i].visible) continue;
if(gd._fullData[i].type !== cn.sankey) continue;
if(!gd._fullData[i]._viewInitial) {
var node = gd._fullData[i].node;
gd._fullData[i]._viewInitial = {
node: {
groups: node.groups.slice(),
x: node.x.slice(),
y: node.y.slice()
}
};
}
}
var linkSelect = function(element, d) {
var evt = d.link;
evt.originalEvent = d3.event;
gd._hoverdata = [evt];
Fx.click(gd, { target: true });
};
var linkHover = function(element, d, sankey) {
if(gd._fullLayout.hovermode === false) return;
d3.select(element).call(linkHoveredStyle.bind(0, d, sankey, true));
if(d.link.trace.link.hoverinfo !== 'skip') {
d.link.fullData = d.link.trace;
gd.emit('plotly_hover', {
event: d3.event,
points: [d.link]
});
}
};
var sourceLabel = _(gd, 'source:') + ' ';
var targetLabel = _(gd, 'target:') + ' ';
var concentrationLabel = _(gd, 'concentration:') + ' ';
var incomingLabel = _(gd, 'incoming flow count:') + ' ';
var outgoingLabel = _(gd, 'outgoing flow count:') + ' ';
var linkHoverFollow = function(element, d) {
if(gd._fullLayout.hovermode === false) return;
var obj = d.link.trace.link;
if(obj.hoverinfo === 'none' || obj.hoverinfo === 'skip') return;
var hoverItems = [];
function hoverCenterPosition(link) {
var hoverCenterX, hoverCenterY;
if(link.circular) {
hoverCenterX = (link.circularPathData.leftInnerExtent + link.circularPathData.rightInnerExtent) / 2;
hoverCenterY = link.circularPathData.verticalFullExtent;
} else {
hoverCenterX = (link.source.x1 + link.target.x0) / 2;
hoverCenterY = (link.y0 + link.y1) / 2;
}
var center = [hoverCenterX, hoverCenterY];
if(link.trace.orientation === 'v') center.reverse();
center[0] += d.parent.translateX;
center[1] += d.parent.translateY;
return center;
}
// For each related links, create a hoverItem
var anchorIndex = 0;
for(var i = 0; i < d.flow.links.length; i++) {
var link = d.flow.links[i];
if(gd._fullLayout.hovermode === 'closest' && d.link.pointNumber !== link.pointNumber) continue;
if(d.link.pointNumber === link.pointNumber) anchorIndex = i;
link.fullData = link.trace;
obj = d.link.trace.link;
var hoverCenter = hoverCenterPosition(link);
var hovertemplateLabels = {valueLabel: numberFormat(d.valueFormat)(link.value) + d.valueSuffix};
hoverItems.push({
x: hoverCenter[0],
y: hoverCenter[1],
name: hovertemplateLabels.valueLabel,
text: [
link.label || '',
sourceLabel + link.source.label,
targetLabel + link.target.label,
link.concentrationscale ? concentrationLabel + numberFormat('%0.2f')(link.flow.labelConcentration) : ''
].filter(renderableValuePresent).join('
'),
color: castHoverOption(obj, 'bgcolor') || Color.addOpacity(link.color, 1),
borderColor: castHoverOption(obj, 'bordercolor'),
fontFamily: castHoverOption(obj, 'font.family'),
fontSize: castHoverOption(obj, 'font.size'),
fontColor: castHoverOption(obj, 'font.color'),
nameLength: castHoverOption(obj, 'namelength'),
textAlign: castHoverOption(obj, 'align'),
idealAlign: d3.event.x < hoverCenter[0] ? 'right' : 'left',
hovertemplate: obj.hovertemplate,
hovertemplateLabels: hovertemplateLabels,
eventData: [link]
});
}
var tooltips = Fx.loneHover(hoverItems, {
container: fullLayout._hoverlayer.node(),
outerContainer: fullLayout._paper.node(),
gd: gd,
anchorIndex: anchorIndex
});
tooltips.each(function() {
var tooltip = this;
if(!d.link.concentrationscale) {
makeTranslucent(tooltip, 0.65);
}
makeTextContrasty(tooltip);
});
};
var linkUnhover = function(element, d, sankey) {
if(gd._fullLayout.hovermode === false) return;
d3.select(element).call(linkNonHoveredStyle.bind(0, d, sankey, true));
if(d.link.trace.link.hoverinfo !== 'skip') {
d.link.fullData = d.link.trace;
gd.emit('plotly_unhover', {
event: d3.event,
points: [d.link]
});
}
Fx.loneUnhover(fullLayout._hoverlayer.node());
};
var nodeSelect = function(element, d, sankey) {
var evt = d.node;
evt.originalEvent = d3.event;
gd._hoverdata = [evt];
d3.select(element).call(nodeNonHoveredStyle, d, sankey);
Fx.click(gd, { target: true });
};
var nodeHover = function(element, d, sankey) {
if(gd._fullLayout.hovermode === false) return;
d3.select(element).call(nodeHoveredStyle, d, sankey);
if(d.node.trace.node.hoverinfo !== 'skip') {
d.node.fullData = d.node.trace;
gd.emit('plotly_hover', {
event: d3.event,
points: [d.node]
});
}
};
var nodeHoverFollow = function(element, d) {
if(gd._fullLayout.hovermode === false) return;
var obj = d.node.trace.node;
if(obj.hoverinfo === 'none' || obj.hoverinfo === 'skip') return;
var nodeRect = d3.select(element).select('.' + cn.nodeRect);
var rootBBox = gd._fullLayout._paperdiv.node().getBoundingClientRect();
var boundingBox = nodeRect.node().getBoundingClientRect();
var hoverCenterX0 = boundingBox.left - 2 - rootBBox.left;
var hoverCenterX1 = boundingBox.right + 2 - rootBBox.left;
var hoverCenterY = boundingBox.top + boundingBox.height / 4 - rootBBox.top;
var hovertemplateLabels = {valueLabel: numberFormat(d.valueFormat)(d.node.value) + d.valueSuffix};
d.node.fullData = d.node.trace;
gd._fullLayout._calcInverseTransform(gd);
var scaleX = gd._fullLayout._invScaleX;
var scaleY = gd._fullLayout._invScaleY;
var tooltip = Fx.loneHover({
x0: scaleX * hoverCenterX0,
x1: scaleX * hoverCenterX1,
y: scaleY * hoverCenterY,
name: numberFormat(d.valueFormat)(d.node.value) + d.valueSuffix,
text: [
d.node.label,
incomingLabel + d.node.targetLinks.length,
outgoingLabel + d.node.sourceLinks.length
].filter(renderableValuePresent).join('
'),
color: castHoverOption(obj, 'bgcolor') || d.tinyColorHue,
borderColor: castHoverOption(obj, 'bordercolor'),
fontFamily: castHoverOption(obj, 'font.family'),
fontSize: castHoverOption(obj, 'font.size'),
fontColor: castHoverOption(obj, 'font.color'),
nameLength: castHoverOption(obj, 'namelength'),
textAlign: castHoverOption(obj, 'align'),
idealAlign: 'left',
hovertemplate: obj.hovertemplate,
hovertemplateLabels: hovertemplateLabels,
eventData: [d.node]
}, {
container: fullLayout._hoverlayer.node(),
outerContainer: fullLayout._paper.node(),
gd: gd
});
makeTranslucent(tooltip, 0.85);
makeTextContrasty(tooltip);
};
var nodeUnhover = function(element, d, sankey) {
if(gd._fullLayout.hovermode === false) return;
d3.select(element).call(nodeNonHoveredStyle, d, sankey);
if(d.node.trace.node.hoverinfo !== 'skip') {
d.node.fullData = d.node.trace;
gd.emit('plotly_unhover', {
event: d3.event,
points: [d.node]
});
}
Fx.loneUnhover(fullLayout._hoverlayer.node());
};
render(
gd,
svg,
calcData,
{
width: size.w,
height: size.h,
margin: {
t: size.t,
r: size.r,
b: size.b,
l: size.l
}
},
{
linkEvents: {
hover: linkHover,
follow: linkHoverFollow,
unhover: linkUnhover,
select: linkSelect
},
nodeEvents: {
hover: nodeHover,
follow: nodeHoverFollow,
unhover: nodeUnhover,
select: nodeSelect
}
}
);
};
},{"../../components/color":366,"../../components/fx":406,"../../lib":503,"./constants":918,"./render":922,"@plotly/d3":58}],922:[function(_dereq_,module,exports){
'use strict';
var d3Force = _dereq_('d3-force');
var interpolateNumber = _dereq_('d3-interpolate').interpolateNumber;
var d3 = _dereq_('@plotly/d3');
var d3Sankey = _dereq_('@plotly/d3-sankey');
var d3SankeyCircular = _dereq_('@plotly/d3-sankey-circular');
var c = _dereq_('./constants');
var tinycolor = _dereq_('tinycolor2');
var Color = _dereq_('../../components/color');
var Drawing = _dereq_('../../components/drawing');
var Lib = _dereq_('../../lib');
var strTranslate = Lib.strTranslate;
var strRotate = Lib.strRotate;
var gup = _dereq_('../../lib/gup');
var keyFun = gup.keyFun;
var repeat = gup.repeat;
var unwrap = gup.unwrap;
var svgTextUtils = _dereq_('../../lib/svg_text_utils');
var Registry = _dereq_('../../registry');
var alignmentConstants = _dereq_('../../constants/alignment');
var CAP_SHIFT = alignmentConstants.CAP_SHIFT;
var LINE_SPACING = alignmentConstants.LINE_SPACING;
var TEXTPAD = 3;
// view models
function sankeyModel(layout, d, traceIndex) {
var calcData = unwrap(d);
var trace = calcData.trace;
var domain = trace.domain;
var horizontal = trace.orientation === 'h';
var nodePad = trace.node.pad;
var nodeThickness = trace.node.thickness;
var width = layout.width * (domain.x[1] - domain.x[0]);
var height = layout.height * (domain.y[1] - domain.y[0]);
var nodes = calcData._nodes;
var links = calcData._links;
var circular = calcData.circular;
// Select Sankey generator
var sankey;
if(circular) {
sankey = d3SankeyCircular
.sankeyCircular()
.circularLinkGap(0);
} else {
sankey = d3Sankey.sankey();
}
sankey
.iterations(c.sankeyIterations)
.size(horizontal ? [width, height] : [height, width])
.nodeWidth(nodeThickness)
.nodePadding(nodePad)
.nodeId(function(d) {
return d.pointNumber;
})
.nodes(nodes)
.links(links);
var graph = sankey();
if(sankey.nodePadding() < nodePad) {
Lib.warn('node.pad was reduced to ', sankey.nodePadding(), ' to fit within the figure.');
}
// Counters for nested loops
var i, j, k;
// Create transient nodes for animations
for(var nodePointNumber in calcData._groupLookup) {
var groupIndex = parseInt(calcData._groupLookup[nodePointNumber]);
// Find node representing groupIndex
var groupingNode;
for(i = 0; i < graph.nodes.length; i++) {
if(graph.nodes[i].pointNumber === groupIndex) {
groupingNode = graph.nodes[i];
break;
}
}
// If groupinNode is undefined, no links are targeting this group
if(!groupingNode) continue;
var child = {
pointNumber: parseInt(nodePointNumber),
x0: groupingNode.x0,
x1: groupingNode.x1,
y0: groupingNode.y0,
y1: groupingNode.y1,
partOfGroup: true,
sourceLinks: [],
targetLinks: []
};
graph.nodes.unshift(child);
groupingNode.childrenNodes.unshift(child);
}
function computeLinkConcentrations() {
for(i = 0; i < graph.nodes.length; i++) {
var node = graph.nodes[i];
// Links connecting the same two nodes are part of a flow
var flows = {};
var flowKey;
var link;
for(j = 0; j < node.targetLinks.length; j++) {
link = node.targetLinks[j];
flowKey = link.source.pointNumber + ':' + link.target.pointNumber;
if(!flows.hasOwnProperty(flowKey)) flows[flowKey] = [];
flows[flowKey].push(link);
}
// Compute statistics for each flow
var keys = Object.keys(flows);
for(j = 0; j < keys.length; j++) {
flowKey = keys[j];
var flowLinks = flows[flowKey];
// Find the total size of the flow and total size per label
var total = 0;
var totalPerLabel = {};
for(k = 0; k < flowLinks.length; k++) {
link = flowLinks[k];
if(!totalPerLabel[link.label]) totalPerLabel[link.label] = 0;
totalPerLabel[link.label] += link.value;
total += link.value;
}
// Find the ratio of the link's value and the size of the flow
for(k = 0; k < flowLinks.length; k++) {
link = flowLinks[k];
link.flow = {
value: total,
labelConcentration: totalPerLabel[link.label] / total,
concentration: link.value / total,
links: flowLinks
};
if(link.concentrationscale) {
link.color = tinycolor(link.concentrationscale(link.flow.labelConcentration));
}
}
}
// Gather statistics of all links at current node
var totalOutflow = 0;
for(j = 0; j < node.sourceLinks.length; j++) {
totalOutflow += node.sourceLinks[j].value;
}
for(j = 0; j < node.sourceLinks.length; j++) {
link = node.sourceLinks[j];
link.concentrationOut = link.value / totalOutflow;
}
var totalInflow = 0;
for(j = 0; j < node.targetLinks.length; j++) {
totalInflow += node.targetLinks[j].value;
}
for(j = 0; j < node.targetLinks.length; j++) {
link = node.targetLinks[j];
link.concenrationIn = link.value / totalInflow;
}
}
}
computeLinkConcentrations();
// Push any overlapping nodes down.
function resolveCollisionsTopToBottom(columns) {
columns.forEach(function(nodes) {
var node;
var dy;
var y = 0;
var n = nodes.length;
var i;
nodes.sort(function(a, b) {
return a.y0 - b.y0;
});
for(i = 0; i < n; ++i) {
node = nodes[i];
if(node.y0 >= y) {
// No overlap
} else {
dy = (y - node.y0);
if(dy > 1e-6) node.y0 += dy, node.y1 += dy;
}
y = node.y1 + nodePad;
}
});
}
// Group nodes into columns based on their x position
function snapToColumns(nodes) {
// Sort nodes by x position
var orderedNodes = nodes.map(function(n, i) {
return {
x0: n.x0,
index: i
};
})
.sort(function(a, b) {
return a.x0 - b.x0;
});
var columns = [];
var colNumber = -1;
var colX; // Position of column
var lastX = -Infinity; // Position of last node
var dx;
for(i = 0; i < orderedNodes.length; i++) {
var node = nodes[orderedNodes[i].index];
// If the node does not overlap with the last one
if(node.x0 > lastX + nodeThickness) {
// Start a new column
colNumber += 1;
colX = node.x0;
}
lastX = node.x0;
// Add node to its associated column
if(!columns[colNumber]) columns[colNumber] = [];
columns[colNumber].push(node);
// Change node's x position to align it with its column
dx = colX - node.x0;
node.x0 += dx, node.x1 += dx;
}
return columns;
}
// Force node position
if(trace.node.x.length && trace.node.y.length) {
for(i = 0; i < Math.min(trace.node.x.length, trace.node.y.length, graph.nodes.length); i++) {
if(trace.node.x[i] && trace.node.y[i]) {
var pos = [trace.node.x[i] * width, trace.node.y[i] * height];
graph.nodes[i].x0 = pos[0] - nodeThickness / 2;
graph.nodes[i].x1 = pos[0] + nodeThickness / 2;
var nodeHeight = graph.nodes[i].y1 - graph.nodes[i].y0;
graph.nodes[i].y0 = pos[1] - nodeHeight / 2;
graph.nodes[i].y1 = pos[1] + nodeHeight / 2;
}
}
if(trace.arrangement === 'snap') {
nodes = graph.nodes;
var columns = snapToColumns(nodes);
resolveCollisionsTopToBottom(columns);
}
// Update links
sankey.update(graph);
}
return {
circular: circular,
key: traceIndex,
trace: trace,
guid: Lib.randstr(),
horizontal: horizontal,
width: width,
height: height,
nodePad: trace.node.pad,
nodeLineColor: trace.node.line.color,
nodeLineWidth: trace.node.line.width,
linkLineColor: trace.link.line.color,
linkLineWidth: trace.link.line.width,
valueFormat: trace.valueformat,
valueSuffix: trace.valuesuffix,
textFont: trace.textfont,
translateX: domain.x[0] * layout.width + layout.margin.l,
translateY: layout.height - domain.y[1] * layout.height + layout.margin.t,
dragParallel: horizontal ? height : width,
dragPerpendicular: horizontal ? width : height,
arrangement: trace.arrangement,
sankey: sankey,
graph: graph,
forceLayouts: {},
interactionState: {
dragInProgress: false,
hovered: false
}
};
}
function linkModel(d, l, i) {
var tc = tinycolor(l.color);
var basicKey = l.source.label + '|' + l.target.label;
var key = basicKey + '__' + i;
// for event data
l.trace = d.trace;
l.curveNumber = d.trace.index;
return {
circular: d.circular,
key: key,
traceId: d.key,
pointNumber: l.pointNumber,
link: l,
tinyColorHue: Color.tinyRGB(tc),
tinyColorAlpha: tc.getAlpha(),
linkPath: linkPath,
linkLineColor: d.linkLineColor,
linkLineWidth: d.linkLineWidth,
valueFormat: d.valueFormat,
valueSuffix: d.valueSuffix,
sankey: d.sankey,
parent: d,
interactionState: d.interactionState,
flow: l.flow
};
}
function createCircularClosedPathString(link) {
// Using coordinates computed by d3-sankey-circular
var pathString = '';
var offset = link.width / 2;
var coords = link.circularPathData;
if(link.circularLinkType === 'top') {
// Top path
pathString =
// start at the left of the target node
'M ' +
coords.targetX + ' ' + (coords.targetY + offset) + ' ' +
'L' +
coords.rightInnerExtent + ' ' + (coords.targetY + offset) +
'A' +
(coords.rightLargeArcRadius + offset) + ' ' + (coords.rightSmallArcRadius + offset) + ' 0 0 1 ' +
(coords.rightFullExtent - offset) + ' ' + (coords.targetY - coords.rightSmallArcRadius) +
'L' +
(coords.rightFullExtent - offset) + ' ' + coords.verticalRightInnerExtent +
'A' +
(coords.rightLargeArcRadius + offset) + ' ' + (coords.rightLargeArcRadius + offset) + ' 0 0 1 ' +
coords.rightInnerExtent + ' ' + (coords.verticalFullExtent - offset) +
'L' +
coords.leftInnerExtent + ' ' + (coords.verticalFullExtent - offset) +
'A' +
(coords.leftLargeArcRadius + offset) + ' ' + (coords.leftLargeArcRadius + offset) + ' 0 0 1 ' +
(coords.leftFullExtent + offset) + ' ' + coords.verticalLeftInnerExtent +
'L' +
(coords.leftFullExtent + offset) + ' ' + (coords.sourceY - coords.leftSmallArcRadius) +
'A' +
(coords.leftLargeArcRadius + offset) + ' ' + (coords.leftSmallArcRadius + offset) + ' 0 0 1 ' +
coords.leftInnerExtent + ' ' + (coords.sourceY + offset) +
'L' +
coords.sourceX + ' ' + (coords.sourceY + offset) +
// Walking back
'L' +
coords.sourceX + ' ' + (coords.sourceY - offset) +
'L' +
coords.leftInnerExtent + ' ' + (coords.sourceY - offset) +
'A' +
(coords.leftLargeArcRadius - offset) + ' ' + (coords.leftSmallArcRadius - offset) + ' 0 0 0 ' +
(coords.leftFullExtent - offset) + ' ' + (coords.sourceY - coords.leftSmallArcRadius) +
'L' +
(coords.leftFullExtent - offset) + ' ' + coords.verticalLeftInnerExtent +
'A' +
(coords.leftLargeArcRadius - offset) + ' ' + (coords.leftLargeArcRadius - offset) + ' 0 0 0 ' +
coords.leftInnerExtent + ' ' + (coords.verticalFullExtent + offset) +
'L' +
coords.rightInnerExtent + ' ' + (coords.verticalFullExtent + offset) +
'A' +
(coords.rightLargeArcRadius - offset) + ' ' + (coords.rightLargeArcRadius - offset) + ' 0 0 0 ' +
(coords.rightFullExtent + offset) + ' ' + coords.verticalRightInnerExtent +
'L' +
(coords.rightFullExtent + offset) + ' ' + (coords.targetY - coords.rightSmallArcRadius) +
'A' +
(coords.rightLargeArcRadius - offset) + ' ' + (coords.rightSmallArcRadius - offset) + ' 0 0 0 ' +
coords.rightInnerExtent + ' ' + (coords.targetY - offset) +
'L' +
coords.targetX + ' ' + (coords.targetY - offset) +
'Z';
} else {
// Bottom path
pathString =
// start at the left of the target node
'M ' +
coords.targetX + ' ' + (coords.targetY - offset) + ' ' +
'L' +
coords.rightInnerExtent + ' ' + (coords.targetY - offset) +
'A' +
(coords.rightLargeArcRadius + offset) + ' ' + (coords.rightSmallArcRadius + offset) + ' 0 0 0 ' +
(coords.rightFullExtent - offset) + ' ' + (coords.targetY + coords.rightSmallArcRadius) +
'L' +
(coords.rightFullExtent - offset) + ' ' + coords.verticalRightInnerExtent +
'A' +
(coords.rightLargeArcRadius + offset) + ' ' + (coords.rightLargeArcRadius + offset) + ' 0 0 0 ' +
coords.rightInnerExtent + ' ' + (coords.verticalFullExtent + offset) +
'L' +
coords.leftInnerExtent + ' ' + (coords.verticalFullExtent + offset) +
'A' +
(coords.leftLargeArcRadius + offset) + ' ' + (coords.leftLargeArcRadius + offset) + ' 0 0 0 ' +
(coords.leftFullExtent + offset) + ' ' + coords.verticalLeftInnerExtent +
'L' +
(coords.leftFullExtent + offset) + ' ' + (coords.sourceY + coords.leftSmallArcRadius) +
'A' +
(coords.leftLargeArcRadius + offset) + ' ' + (coords.leftSmallArcRadius + offset) + ' 0 0 0 ' +
coords.leftInnerExtent + ' ' + (coords.sourceY - offset) +
'L' +
coords.sourceX + ' ' + (coords.sourceY - offset) +
// Walking back
'L' +
coords.sourceX + ' ' + (coords.sourceY + offset) +
'L' +
coords.leftInnerExtent + ' ' + (coords.sourceY + offset) +
'A' +
(coords.leftLargeArcRadius - offset) + ' ' + (coords.leftSmallArcRadius - offset) + ' 0 0 1 ' +
(coords.leftFullExtent - offset) + ' ' + (coords.sourceY + coords.leftSmallArcRadius) +
'L' +
(coords.leftFullExtent - offset) + ' ' + coords.verticalLeftInnerExtent +
'A' +
(coords.leftLargeArcRadius - offset) + ' ' + (coords.leftLargeArcRadius - offset) + ' 0 0 1 ' +
coords.leftInnerExtent + ' ' + (coords.verticalFullExtent - offset) +
'L' +
coords.rightInnerExtent + ' ' + (coords.verticalFullExtent - offset) +
'A' +
(coords.rightLargeArcRadius - offset) + ' ' + (coords.rightLargeArcRadius - offset) + ' 0 0 1 ' +
(coords.rightFullExtent + offset) + ' ' + coords.verticalRightInnerExtent +
'L' +
(coords.rightFullExtent + offset) + ' ' + (coords.targetY + coords.rightSmallArcRadius) +
'A' +
(coords.rightLargeArcRadius - offset) + ' ' + (coords.rightSmallArcRadius - offset) + ' 0 0 1 ' +
coords.rightInnerExtent + ' ' + (coords.targetY + offset) +
'L' +
coords.targetX + ' ' + (coords.targetY + offset) +
'Z';
}
return pathString;
}
function linkPath() {
var curvature = 0.5;
function path(d) {
if(d.link.circular) {
return createCircularClosedPathString(d.link);
} else {
var x0 = d.link.source.x1;
var x1 = d.link.target.x0;
var xi = interpolateNumber(x0, x1);
var x2 = xi(curvature);
var x3 = xi(1 - curvature);
var y0a = d.link.y0 - d.link.width / 2;
var y0b = d.link.y0 + d.link.width / 2;
var y1a = d.link.y1 - d.link.width / 2;
var y1b = d.link.y1 + d.link.width / 2;
return 'M' + x0 + ',' + y0a +
'C' + x2 + ',' + y0a +
' ' + x3 + ',' + y1a +
' ' + x1 + ',' + y1a +
'L' + x1 + ',' + y1b +
'C' + x3 + ',' + y1b +
' ' + x2 + ',' + y0b +
' ' + x0 + ',' + y0b +
'Z';
}
}
return path;
}
function nodeModel(d, n) {
var tc = tinycolor(n.color);
var zoneThicknessPad = c.nodePadAcross;
var zoneLengthPad = d.nodePad / 2;
n.dx = n.x1 - n.x0;
n.dy = n.y1 - n.y0;
var visibleThickness = n.dx;
var visibleLength = Math.max(0.5, n.dy);
var key = 'node_' + n.pointNumber;
// If it's a group, it's mutable and should be unique
if(n.group) {
key = Lib.randstr();
}
// for event data
n.trace = d.trace;
n.curveNumber = d.trace.index;
return {
index: n.pointNumber,
key: key,
partOfGroup: n.partOfGroup || false,
group: n.group,
traceId: d.key,
trace: d.trace,
node: n,
nodePad: d.nodePad,
nodeLineColor: d.nodeLineColor,
nodeLineWidth: d.nodeLineWidth,
textFont: d.textFont,
size: d.horizontal ? d.height : d.width,
visibleWidth: Math.ceil(visibleThickness),
visibleHeight: visibleLength,
zoneX: -zoneThicknessPad,
zoneY: -zoneLengthPad,
zoneWidth: visibleThickness + 2 * zoneThicknessPad,
zoneHeight: visibleLength + 2 * zoneLengthPad,
labelY: d.horizontal ? n.dy / 2 + 1 : n.dx / 2 + 1,
left: n.originalLayer === 1,
sizeAcross: d.width,
forceLayouts: d.forceLayouts,
horizontal: d.horizontal,
darkBackground: tc.getBrightness() <= 128,
tinyColorHue: Color.tinyRGB(tc),
tinyColorAlpha: tc.getAlpha(),
valueFormat: d.valueFormat,
valueSuffix: d.valueSuffix,
sankey: d.sankey,
graph: d.graph,
arrangement: d.arrangement,
uniqueNodeLabelPathId: [d.guid, d.key, key].join('_'),
interactionState: d.interactionState,
figure: d
};
}
// rendering snippets
function updateNodePositions(sankeyNode) {
sankeyNode
.attr('transform', function(d) {
return strTranslate(d.node.x0.toFixed(3), (d.node.y0).toFixed(3));
});
}
function updateNodeShapes(sankeyNode) {
sankeyNode.call(updateNodePositions);
}
function updateShapes(sankeyNode, sankeyLink) {
sankeyNode.call(updateNodeShapes);
sankeyLink.attr('d', linkPath());
}
function sizeNode(rect) {
rect
.attr('width', function(d) {return d.node.x1 - d.node.x0;})
.attr('height', function(d) {return d.visibleHeight;});
}
function salientEnough(d) {return (d.link.width > 1 || d.linkLineWidth > 0);}
function sankeyTransform(d) {
var offset = strTranslate(d.translateX, d.translateY);
return offset + (d.horizontal ? 'matrix(1 0 0 1 0 0)' : 'matrix(0 1 1 0 0 0)');
}
// event handling
function attachPointerEvents(selection, sankey, eventSet) {
selection
.on('.basic', null) // remove any preexisting handlers
.on('mouseover.basic', function(d) {
if(!d.interactionState.dragInProgress && !d.partOfGroup) {
eventSet.hover(this, d, sankey);
d.interactionState.hovered = [this, d];
}
})
.on('mousemove.basic', function(d) {
if(!d.interactionState.dragInProgress && !d.partOfGroup) {
eventSet.follow(this, d);
d.interactionState.hovered = [this, d];
}
})
.on('mouseout.basic', function(d) {
if(!d.interactionState.dragInProgress && !d.partOfGroup) {
eventSet.unhover(this, d, sankey);
d.interactionState.hovered = false;
}
})
.on('click.basic', function(d) {
if(d.interactionState.hovered) {
eventSet.unhover(this, d, sankey);
d.interactionState.hovered = false;
}
if(!d.interactionState.dragInProgress && !d.partOfGroup) {
eventSet.select(this, d, sankey);
}
});
}
function attachDragHandler(sankeyNode, sankeyLink, callbacks, gd) {
var dragBehavior = d3.behavior.drag()
.origin(function(d) {
return {
x: d.node.x0 + d.visibleWidth / 2,
y: d.node.y0 + d.visibleHeight / 2
};
})
.on('dragstart', function(d) {
if(d.arrangement === 'fixed') return;
Lib.ensureSingle(gd._fullLayout._infolayer, 'g', 'dragcover', function(s) {
gd._fullLayout._dragCover = s;
});
Lib.raiseToTop(this);
d.interactionState.dragInProgress = d.node;
saveCurrentDragPosition(d.node);
if(d.interactionState.hovered) {
callbacks.nodeEvents.unhover.apply(0, d.interactionState.hovered);
d.interactionState.hovered = false;
}
if(d.arrangement === 'snap') {
var forceKey = d.traceId + '|' + d.key;
if(d.forceLayouts[forceKey]) {
d.forceLayouts[forceKey].alpha(1);
} else { // make a forceLayout if needed
attachForce(sankeyNode, forceKey, d, gd);
}
startForce(sankeyNode, sankeyLink, d, forceKey, gd);
}
})
.on('drag', function(d) {
if(d.arrangement === 'fixed') return;
var x = d3.event.x;
var y = d3.event.y;
if(d.arrangement === 'snap') {
d.node.x0 = x - d.visibleWidth / 2;
d.node.x1 = x + d.visibleWidth / 2;
d.node.y0 = y - d.visibleHeight / 2;
d.node.y1 = y + d.visibleHeight / 2;
} else {
if(d.arrangement === 'freeform') {
d.node.x0 = x - d.visibleWidth / 2;
d.node.x1 = x + d.visibleWidth / 2;
}
y = Math.max(0, Math.min(d.size - d.visibleHeight / 2, y));
d.node.y0 = y - d.visibleHeight / 2;
d.node.y1 = y + d.visibleHeight / 2;
}
saveCurrentDragPosition(d.node);
if(d.arrangement !== 'snap') {
d.sankey.update(d.graph);
updateShapes(sankeyNode.filter(sameLayer(d)), sankeyLink);
}
})
.on('dragend', function(d) {
if(d.arrangement === 'fixed') return;
d.interactionState.dragInProgress = false;
for(var i = 0; i < d.node.childrenNodes.length; i++) {
d.node.childrenNodes[i].x = d.node.x;
d.node.childrenNodes[i].y = d.node.y;
}
if(d.arrangement !== 'snap') persistFinalNodePositions(d, gd);
});
sankeyNode
.on('.drag', null) // remove possible previous handlers
.call(dragBehavior);
}
function attachForce(sankeyNode, forceKey, d, gd) {
// Attach force to nodes in the same column (same x coordinate)
switchToForceFormat(d.graph.nodes);
var nodes = d.graph.nodes
.filter(function(n) {return n.originalX === d.node.originalX;})
// Filter out children
.filter(function(n) {return !n.partOfGroup;});
d.forceLayouts[forceKey] = d3Force.forceSimulation(nodes)
.alphaDecay(0)
.force('collide', d3Force.forceCollide()
.radius(function(n) {return n.dy / 2 + d.nodePad / 2;})
.strength(1)
.iterations(c.forceIterations))
.force('constrain', snappingForce(sankeyNode, forceKey, nodes, d, gd))
.stop();
}
function startForce(sankeyNode, sankeyLink, d, forceKey, gd) {
window.requestAnimationFrame(function faster() {
var i;
for(i = 0; i < c.forceTicksPerFrame; i++) {
d.forceLayouts[forceKey].tick();
}
var nodes = d.graph.nodes;
switchToSankeyFormat(nodes);
d.sankey.update(d.graph);
updateShapes(sankeyNode.filter(sameLayer(d)), sankeyLink);
if(d.forceLayouts[forceKey].alpha() > 0) {
window.requestAnimationFrame(faster);
} else {
// Make sure the final x position is equal to its original value
// because the force simulation will have numerical error
var x = d.node.originalX;
d.node.x0 = x - d.visibleWidth / 2;
d.node.x1 = x + d.visibleWidth / 2;
persistFinalNodePositions(d, gd);
}
});
}
function snappingForce(sankeyNode, forceKey, nodes, d) {
return function _snappingForce() {
var maxVelocity = 0;
for(var i = 0; i < nodes.length; i++) {
var n = nodes[i];
if(n === d.interactionState.dragInProgress) { // constrain node position to the dragging pointer
n.x = n.lastDraggedX;
n.y = n.lastDraggedY;
} else {
n.vx = (n.originalX - n.x) / c.forceTicksPerFrame; // snap to layer
n.y = Math.min(d.size - n.dy / 2, Math.max(n.dy / 2, n.y)); // constrain to extent
}
maxVelocity = Math.max(maxVelocity, Math.abs(n.vx), Math.abs(n.vy));
}
if(!d.interactionState.dragInProgress && maxVelocity < 0.1 && d.forceLayouts[forceKey].alpha() > 0) {
d.forceLayouts[forceKey].alpha(0); // This will stop the animation loop
}
};
}
// basic data utilities
function persistFinalNodePositions(d, gd) {
var x = [];
var y = [];
for(var i = 0; i < d.graph.nodes.length; i++) {
var nodeX = (d.graph.nodes[i].x0 + d.graph.nodes[i].x1) / 2;
var nodeY = (d.graph.nodes[i].y0 + d.graph.nodes[i].y1) / 2;
x.push(nodeX / d.figure.width);
y.push(nodeY / d.figure.height);
}
Registry.call('_guiRestyle', gd, {
'node.x': [x],
'node.y': [y]
}, d.trace.index)
.then(function() {
if(gd._fullLayout._dragCover) gd._fullLayout._dragCover.remove();
});
}
function persistOriginalPlace(nodes) {
var distinctLayerPositions = [];
var i;
for(i = 0; i < nodes.length; i++) {
nodes[i].originalX = (nodes[i].x0 + nodes[i].x1) / 2;
nodes[i].originalY = (nodes[i].y0 + nodes[i].y1) / 2;
if(distinctLayerPositions.indexOf(nodes[i].originalX) === -1) {
distinctLayerPositions.push(nodes[i].originalX);
}
}
distinctLayerPositions.sort(function(a, b) {return a - b;});
for(i = 0; i < nodes.length; i++) {
nodes[i].originalLayerIndex = distinctLayerPositions.indexOf(nodes[i].originalX);
nodes[i].originalLayer = nodes[i].originalLayerIndex / (distinctLayerPositions.length - 1);
}
}
function saveCurrentDragPosition(d) {
d.lastDraggedX = d.x0 + d.dx / 2;
d.lastDraggedY = d.y0 + d.dy / 2;
}
function sameLayer(d) {
return function(n) {return n.node.originalX === d.node.originalX;};
}
function switchToForceFormat(nodes) {
// force uses x, y as centers
for(var i = 0; i < nodes.length; i++) {
nodes[i].y = (nodes[i].y0 + nodes[i].y1) / 2;
nodes[i].x = (nodes[i].x0 + nodes[i].x1) / 2;
}
}
function switchToSankeyFormat(nodes) {
// sankey uses x0, x1, y0, y1
for(var i = 0; i < nodes.length; i++) {
nodes[i].y0 = nodes[i].y - nodes[i].dy / 2;
nodes[i].y1 = nodes[i].y0 + nodes[i].dy;
nodes[i].x0 = nodes[i].x - nodes[i].dx / 2;
nodes[i].x1 = nodes[i].x0 + nodes[i].dx;
}
}
// scene graph
module.exports = function(gd, svg, calcData, layout, callbacks) {
// To prevent animation on first render
var firstRender = false;
Lib.ensureSingle(gd._fullLayout._infolayer, 'g', 'first-render', function() {
firstRender = true;
});
// To prevent animation on dragging
var dragcover = gd._fullLayout._dragCover;
var styledData = calcData
.filter(function(d) {return unwrap(d).trace.visible;})
.map(sankeyModel.bind(null, layout));
var sankey = svg.selectAll('.' + c.cn.sankey)
.data(styledData, keyFun);
sankey.exit()
.remove();
sankey.enter()
.append('g')
.classed(c.cn.sankey, true)
.style('box-sizing', 'content-box')
.style('position', 'absolute')
.style('left', 0)
.style('shape-rendering', 'geometricPrecision')
.style('pointer-events', 'auto')
.attr('transform', sankeyTransform);
sankey.each(function(d, i) {
gd._fullData[i]._sankey = d;
// Create dragbox if missing
var dragboxClassName = 'bgsankey-' + d.trace.uid + '-' + i;
Lib.ensureSingle(gd._fullLayout._draggers, 'rect', dragboxClassName);
gd._fullData[i]._bgRect = d3.select('.' + dragboxClassName);
// Style dragbox
gd._fullData[i]._bgRect
.style('pointer-events', 'all')
.attr('width', d.width)
.attr('height', d.height)
.attr('x', d.translateX)
.attr('y', d.translateY)
.classed('bgsankey', true)
.style({fill: 'transparent', 'stroke-width': 0});
});
sankey.transition()
.ease(c.ease).duration(c.duration)
.attr('transform', sankeyTransform);
var sankeyLinks = sankey.selectAll('.' + c.cn.sankeyLinks)
.data(repeat, keyFun);
sankeyLinks.enter()
.append('g')
.classed(c.cn.sankeyLinks, true)
.style('fill', 'none');
var sankeyLink = sankeyLinks.selectAll('.' + c.cn.sankeyLink)
.data(function(d) {
var links = d.graph.links;
return links
.filter(function(l) {return l.value;})
.map(linkModel.bind(null, d));
}, keyFun);
sankeyLink
.enter().append('path')
.classed(c.cn.sankeyLink, true)
.call(attachPointerEvents, sankey, callbacks.linkEvents);
sankeyLink
.style('stroke', function(d) {
return salientEnough(d) ? Color.tinyRGB(tinycolor(d.linkLineColor)) : d.tinyColorHue;
})
.style('stroke-opacity', function(d) {
return salientEnough(d) ? Color.opacity(d.linkLineColor) : d.tinyColorAlpha;
})
.style('fill', function(d) {
return d.tinyColorHue;
})
.style('fill-opacity', function(d) {
return d.tinyColorAlpha;
})
.style('stroke-width', function(d) {
return salientEnough(d) ? d.linkLineWidth : 1;
})
.attr('d', linkPath());
sankeyLink
.style('opacity', function() { return (gd._context.staticPlot || firstRender || dragcover) ? 1 : 0;})
.transition()
.ease(c.ease).duration(c.duration)
.style('opacity', 1);
sankeyLink.exit()
.transition()
.ease(c.ease).duration(c.duration)
.style('opacity', 0)
.remove();
var sankeyNodeSet = sankey.selectAll('.' + c.cn.sankeyNodeSet)
.data(repeat, keyFun);
sankeyNodeSet.enter()
.append('g')
.classed(c.cn.sankeyNodeSet, true);
sankeyNodeSet
.style('cursor', function(d) {
switch(d.arrangement) {
case 'fixed': return 'default';
case 'perpendicular': return 'ns-resize';
default: return 'move';
}
});
var sankeyNode = sankeyNodeSet.selectAll('.' + c.cn.sankeyNode)
.data(function(d) {
var nodes = d.graph.nodes;
persistOriginalPlace(nodes);
return nodes
.map(nodeModel.bind(null, d));
}, keyFun);
sankeyNode.enter()
.append('g')
.classed(c.cn.sankeyNode, true)
.call(updateNodePositions)
.style('opacity', function(n) { return ((gd._context.staticPlot || firstRender) && !n.partOfGroup) ? 1 : 0;});
sankeyNode
.call(attachPointerEvents, sankey, callbacks.nodeEvents)
.call(attachDragHandler, sankeyLink, callbacks, gd); // has to be here as it binds sankeyLink
sankeyNode
.transition()
.ease(c.ease).duration(c.duration)
.call(updateNodePositions)
.style('opacity', function(n) { return n.partOfGroup ? 0 : 1;});
sankeyNode.exit()
.transition()
.ease(c.ease).duration(c.duration)
.style('opacity', 0)
.remove();
var nodeRect = sankeyNode.selectAll('.' + c.cn.nodeRect)
.data(repeat);
nodeRect.enter()
.append('rect')
.classed(c.cn.nodeRect, true)
.call(sizeNode);
nodeRect
.style('stroke-width', function(d) {return d.nodeLineWidth;})
.style('stroke', function(d) {return Color.tinyRGB(tinycolor(d.nodeLineColor));})
.style('stroke-opacity', function(d) {return Color.opacity(d.nodeLineColor);})
.style('fill', function(d) {return d.tinyColorHue;})
.style('fill-opacity', function(d) {return d.tinyColorAlpha;});
nodeRect.transition()
.ease(c.ease).duration(c.duration)
.call(sizeNode);
var nodeLabel = sankeyNode.selectAll('.' + c.cn.nodeLabel)
.data(repeat);
nodeLabel.enter()
.append('text')
.classed(c.cn.nodeLabel, true)
.style('cursor', 'default');
nodeLabel
.attr('data-notex', 1) // prohibit tex interpretation until we can handle tex and regular text together
.text(function(d) { return d.node.label; })
.each(function(d) {
var e = d3.select(this);
Drawing.font(e, d.textFont);
svgTextUtils.convertToTspans(e, gd);
})
.style('text-shadow', svgTextUtils.makeTextShadow(gd._fullLayout.paper_bgcolor))
.attr('text-anchor', function(d) {
return (d.horizontal && d.left) ? 'end' : 'start';
})
.attr('transform', function(d) {
var e = d3.select(this);
// how much to shift a multi-line label to center it vertically.
var nLines = svgTextUtils.lineCount(e);
var blockHeight = d.textFont.size * (
(nLines - 1) * LINE_SPACING - CAP_SHIFT
);
var posX = d.nodeLineWidth / 2 + TEXTPAD;
var posY = ((d.horizontal ? d.visibleHeight : d.visibleWidth) - blockHeight) / 2;
if(d.horizontal) {
if(d.left) {
posX = -posX;
} else {
posX += d.visibleWidth;
}
}
var flipText = d.horizontal ? '' : (
'scale(-1,1)' + strRotate(90)
);
return strTranslate(
d.horizontal ? posX : posY,
d.horizontal ? posY : posX
) + flipText;
});
nodeLabel
.transition()
.ease(c.ease).duration(c.duration);
};
},{"../../components/color":366,"../../components/drawing":388,"../../constants/alignment":471,"../../lib":503,"../../lib/gup":500,"../../lib/svg_text_utils":529,"../../registry":638,"./constants":918,"@plotly/d3":58,"@plotly/d3-sankey":57,"@plotly/d3-sankey-circular":56,"d3-force":111,"d3-interpolate":116,"tinycolor2":312}],923:[function(_dereq_,module,exports){
'use strict';
module.exports = function selectPoints(searchInfo, selectionTester) {
var cd = searchInfo.cd;
var selection = [];
var fullData = cd[0].trace;
var nodes = fullData._sankey.graph.nodes;
for(var i = 0; i < nodes.length; i++) {
var node = nodes[i];
if(node.partOfGroup) continue; // Those are invisible
// Position of node's centroid
var pos = [(node.x0 + node.x1) / 2, (node.y0 + node.y1) / 2];
// Swap x and y if trace is vertical
if(fullData.orientation === 'v') pos.reverse();
if(selectionTester && selectionTester.contains(pos, false, i, searchInfo)) {
selection.push({
pointNumber: node.pointNumber
// TODO: add eventData
});
}
}
return selection;
};
},{}],924:[function(_dereq_,module,exports){
'use strict';
var Lib = _dereq_('../../lib');
// arrayOk attributes, merge them into calcdata array
module.exports = function arraysToCalcdata(cd, trace) {
// so each point knows which index it originally came from
for(var i = 0; i < cd.length; i++) cd[i].i = i;
Lib.mergeArray(trace.text, cd, 'tx');
Lib.mergeArray(trace.texttemplate, cd, 'txt');
Lib.mergeArray(trace.hovertext, cd, 'htx');
Lib.mergeArray(trace.customdata, cd, 'data');
Lib.mergeArray(trace.textposition, cd, 'tp');
if(trace.textfont) {
Lib.mergeArrayCastPositive(trace.textfont.size, cd, 'ts');
Lib.mergeArray(trace.textfont.color, cd, 'tc');
Lib.mergeArray(trace.textfont.family, cd, 'tf');
}
var marker = trace.marker;
if(marker) {
Lib.mergeArrayCastPositive(marker.size, cd, 'ms');
Lib.mergeArrayCastPositive(marker.opacity, cd, 'mo');
Lib.mergeArray(marker.symbol, cd, 'mx');
Lib.mergeArray(marker.color, cd, 'mc');
var markerLine = marker.line;
if(marker.line) {
Lib.mergeArray(markerLine.color, cd, 'mlc');
Lib.mergeArrayCastPositive(markerLine.width, cd, 'mlw');
}
var markerGradient = marker.gradient;
if(markerGradient && markerGradient.type !== 'none') {
Lib.mergeArray(markerGradient.type, cd, 'mgt');
Lib.mergeArray(markerGradient.color, cd, 'mgc');
}
}
};
},{"../../lib":503}],925:[function(_dereq_,module,exports){
'use strict';
var axisHoverFormat = _dereq_('../../plots/cartesian/axis_format_attributes').axisHoverFormat;
var texttemplateAttrs = _dereq_('../../plots/template_attributes').texttemplateAttrs;
var hovertemplateAttrs = _dereq_('../../plots/template_attributes').hovertemplateAttrs;
var colorScaleAttrs = _dereq_('../../components/colorscale/attributes');
var fontAttrs = _dereq_('../../plots/font_attributes');
var dash = _dereq_('../../components/drawing/attributes').dash;
var Drawing = _dereq_('../../components/drawing');
var constants = _dereq_('./constants');
var extendFlat = _dereq_('../../lib/extend').extendFlat;
function axisPeriod(axis) {
return {
valType: 'any',
dflt: 0,
editType: 'calc',
};
}
function axisPeriod0(axis) {
return {
valType: 'any',
editType: 'calc',
};
}
function axisPeriodAlignment(axis) {
return {
valType: 'enumerated',
values: [
'start', 'middle', 'end'
],
dflt: 'middle',
editType: 'calc',
};
}
module.exports = {
x: {
valType: 'data_array',
editType: 'calc+clearAxisTypes',
anim: true,
},
x0: {
valType: 'any',
dflt: 0,
editType: 'calc+clearAxisTypes',
anim: true,
},
dx: {
valType: 'number',
dflt: 1,
editType: 'calc',
anim: true,
},
y: {
valType: 'data_array',
editType: 'calc+clearAxisTypes',
anim: true,
},
y0: {
valType: 'any',
dflt: 0,
editType: 'calc+clearAxisTypes',
anim: true,
},
dy: {
valType: 'number',
dflt: 1,
editType: 'calc',
anim: true,
},
xperiod: axisPeriod('x'),
yperiod: axisPeriod('y'),
xperiod0: axisPeriod0('x0'),
yperiod0: axisPeriod0('y0'),
xperiodalignment: axisPeriodAlignment('x'),
yperiodalignment: axisPeriodAlignment('y'),
xhoverformat: axisHoverFormat('x'),
yhoverformat: axisHoverFormat('y'),
stackgroup: {
valType: 'string',
dflt: '',
editType: 'calc',
},
orientation: {
valType: 'enumerated',
values: ['v', 'h'],
editType: 'calc',
},
groupnorm: {
valType: 'enumerated',
values: ['', 'fraction', 'percent'],
dflt: '',
editType: 'calc',
},
stackgaps: {
valType: 'enumerated',
values: ['infer zero', 'interpolate'],
dflt: 'infer zero',
editType: 'calc',
},
text: {
valType: 'string',
dflt: '',
arrayOk: true,
editType: 'calc',
},
texttemplate: texttemplateAttrs({}, {
}),
hovertext: {
valType: 'string',
dflt: '',
arrayOk: true,
editType: 'style',
},
mode: {
valType: 'flaglist',
flags: ['lines', 'markers', 'text'],
extras: ['none'],
editType: 'calc',
},
hoveron: {
valType: 'flaglist',
flags: ['points', 'fills'],
editType: 'style',
},
hovertemplate: hovertemplateAttrs({}, {
keys: constants.eventDataKeys
}),
line: {
color: {
valType: 'color',
editType: 'style',
anim: true,
},
width: {
valType: 'number',
min: 0,
dflt: 2,
editType: 'style',
anim: true,
},
shape: {
valType: 'enumerated',
values: ['linear', 'spline', 'hv', 'vh', 'hvh', 'vhv'],
dflt: 'linear',
editType: 'plot',
},
smoothing: {
valType: 'number',
min: 0,
max: 1.3,
dflt: 1,
editType: 'plot',
},
dash: extendFlat({}, dash, {editType: 'style'}),
simplify: {
valType: 'boolean',
dflt: true,
editType: 'plot',
},
editType: 'plot'
},
connectgaps: {
valType: 'boolean',
dflt: false,
editType: 'calc',
},
cliponaxis: {
valType: 'boolean',
dflt: true,
editType: 'plot',
},
fill: {
valType: 'enumerated',
values: ['none', 'tozeroy', 'tozerox', 'tonexty', 'tonextx', 'toself', 'tonext'],
editType: 'calc',
},
fillcolor: {
valType: 'color',
editType: 'style',
anim: true,
},
marker: extendFlat({
symbol: {
valType: 'enumerated',
values: Drawing.symbolList,
dflt: 'circle',
arrayOk: true,
editType: 'style',
},
opacity: {
valType: 'number',
min: 0,
max: 1,
arrayOk: true,
editType: 'style',
anim: true,
},
size: {
valType: 'number',
min: 0,
dflt: 6,
arrayOk: true,
editType: 'calc',
anim: true,
},
maxdisplayed: {
valType: 'number',
min: 0,
dflt: 0,
editType: 'plot',
},
sizeref: {
valType: 'number',
dflt: 1,
editType: 'calc',
},
sizemin: {
valType: 'number',
min: 0,
dflt: 0,
editType: 'calc',
},
sizemode: {
valType: 'enumerated',
values: ['diameter', 'area'],
dflt: 'diameter',
editType: 'calc',
},
line: extendFlat({
width: {
valType: 'number',
min: 0,
arrayOk: true,
editType: 'style',
anim: true,
},
editType: 'calc'
},
colorScaleAttrs('marker.line', {anim: true})
),
gradient: {
type: {
valType: 'enumerated',
values: ['radial', 'horizontal', 'vertical', 'none'],
arrayOk: true,
dflt: 'none',
editType: 'calc',
},
color: {
valType: 'color',
arrayOk: true,
editType: 'calc',
},
editType: 'calc'
},
editType: 'calc'
},
colorScaleAttrs('marker', {anim: true})
),
selected: {
marker: {
opacity: {
valType: 'number',
min: 0,
max: 1,
editType: 'style',
},
color: {
valType: 'color',
editType: 'style',
},
size: {
valType: 'number',
min: 0,
editType: 'style',
},
editType: 'style'
},
textfont: {
color: {
valType: 'color',
editType: 'style',
},
editType: 'style'
},
editType: 'style'
},
unselected: {
marker: {
opacity: {
valType: 'number',
min: 0,
max: 1,
editType: 'style',
},
color: {
valType: 'color',
editType: 'style',
},
size: {
valType: 'number',
min: 0,
editType: 'style',
},
editType: 'style'
},
textfont: {
color: {
valType: 'color',
editType: 'style',
},
editType: 'style'
},
editType: 'style'
},
textposition: {
valType: 'enumerated',
values: [
'top left', 'top center', 'top right',
'middle left', 'middle center', 'middle right',
'bottom left', 'bottom center', 'bottom right'
],
dflt: 'middle center',
arrayOk: true,
editType: 'calc',
},
textfont: fontAttrs({
editType: 'calc',
colorEditType: 'style',
arrayOk: true,
}),
};
},{"../../components/colorscale/attributes":373,"../../components/drawing":388,"../../components/drawing/attributes":387,"../../lib/extend":493,"../../plots/cartesian/axis_format_attributes":557,"../../plots/font_attributes":585,"../../plots/template_attributes":633,"./constants":929}],926:[function(_dereq_,module,exports){
'use strict';
var isNumeric = _dereq_('fast-isnumeric');
var Lib = _dereq_('../../lib');
var Axes = _dereq_('../../plots/cartesian/axes');
var alignPeriod = _dereq_('../../plots/cartesian/align_period');
var BADNUM = _dereq_('../../constants/numerical').BADNUM;
var subTypes = _dereq_('./subtypes');
var calcColorscale = _dereq_('./colorscale_calc');
var arraysToCalcdata = _dereq_('./arrays_to_calcdata');
var calcSelection = _dereq_('./calc_selection');
function calc(gd, trace) {
var fullLayout = gd._fullLayout;
var xa = Axes.getFromId(gd, trace.xaxis || 'x');
var ya = Axes.getFromId(gd, trace.yaxis || 'y');
var origX = xa.makeCalcdata(trace, 'x');
var origY = ya.makeCalcdata(trace, 'y');
var xObj = alignPeriod(trace, xa, 'x', origX);
var yObj = alignPeriod(trace, ya, 'y', origY);
var x = xObj.vals;
var y = yObj.vals;
var serieslen = trace._length;
var cd = new Array(serieslen);
var ids = trace.ids;
var stackGroupOpts = getStackOpts(trace, fullLayout, xa, ya);
var interpolateGaps = false;
var isV, i, j, k, interpolate, vali;
setFirstScatter(fullLayout, trace);
var xAttr = 'x';
var yAttr = 'y';
var posAttr;
if(stackGroupOpts) {
Lib.pushUnique(stackGroupOpts.traceIndices, trace._expandedIndex);
isV = stackGroupOpts.orientation === 'v';
// size, like we use for bar
if(isV) {
yAttr = 's';
posAttr = 'x';
} else {
xAttr = 's';
posAttr = 'y';
}
interpolate = stackGroupOpts.stackgaps === 'interpolate';
} else {
var ppad = calcMarkerSize(trace, serieslen);
calcAxisExpansion(gd, trace, xa, ya, x, y, ppad);
}
var hasPeriodX = !!trace.xperiodalignment;
var hasPeriodY = !!trace.yperiodalignment;
for(i = 0; i < serieslen; i++) {
var cdi = cd[i] = {};
var xValid = isNumeric(x[i]);
var yValid = isNumeric(y[i]);
if(xValid && yValid) {
cdi[xAttr] = x[i];
cdi[yAttr] = y[i];
if(hasPeriodX) {
cdi.orig_x = origX[i]; // used by hover
cdi.xEnd = xObj.ends[i];
cdi.xStart = xObj.starts[i];
}
if(hasPeriodY) {
cdi.orig_y = origY[i]; // used by hover
cdi.yEnd = yObj.ends[i];
cdi.yStart = yObj.starts[i];
}
} else if(stackGroupOpts && (isV ? xValid : yValid)) {
// if we're stacking we need to hold on to all valid positions
// even with invalid sizes
cdi[posAttr] = isV ? x[i] : y[i];
cdi.gap = true;
if(interpolate) {
cdi.s = BADNUM;
interpolateGaps = true;
} else {
cdi.s = 0;
}
} else {
cdi[xAttr] = cdi[yAttr] = BADNUM;
}
if(ids) {
cdi.id = String(ids[i]);
}
}
arraysToCalcdata(cd, trace);
calcColorscale(gd, trace);
calcSelection(cd, trace);
if(stackGroupOpts) {
// remove bad positions and sort
// note that original indices get added to cd in arraysToCalcdata
i = 0;
while(i < cd.length) {
if(cd[i][posAttr] === BADNUM) {
cd.splice(i, 1);
} else i++;
}
Lib.sort(cd, function(a, b) {
return (a[posAttr] - b[posAttr]) || (a.i - b.i);
});
if(interpolateGaps) {
// first fill the beginning with constant from the first point
i = 0;
while(i < cd.length - 1 && cd[i].gap) {
i++;
}
vali = cd[i].s;
if(!vali) vali = cd[i].s = 0; // in case of no data AT ALL in this trace - use 0
for(j = 0; j < i; j++) {
cd[j].s = vali;
}
// then fill the end with constant from the last point
k = cd.length - 1;
while(k > i && cd[k].gap) {
k--;
}
vali = cd[k].s;
for(j = cd.length - 1; j > k; j--) {
cd[j].s = vali;
}
// now interpolate internal gaps linearly
while(i < k) {
i++;
if(cd[i].gap) {
j = i + 1;
while(cd[j].gap) {
j++;
}
var pos0 = cd[i - 1][posAttr];
var size0 = cd[i - 1].s;
var m = (cd[j].s - size0) / (cd[j][posAttr] - pos0);
while(i < j) {
cd[i].s = size0 + (cd[i][posAttr] - pos0) * m;
i++;
}
}
}
}
}
return cd;
}
function calcAxisExpansion(gd, trace, xa, ya, x, y, ppad) {
var serieslen = trace._length;
var fullLayout = gd._fullLayout;
var xId = xa._id;
var yId = ya._id;
var firstScatter = fullLayout._firstScatter[firstScatterGroup(trace)] === trace.uid;
var stackOrientation = (getStackOpts(trace, fullLayout, xa, ya) || {}).orientation;
var fill = trace.fill;
// cancel minimum tick spacings (only applies to bars and boxes)
xa._minDtick = 0;
ya._minDtick = 0;
// check whether bounds should be tight, padded, extended to zero...
// most cases both should be padded on both ends, so start with that.
var xOptions = {padded: true};
var yOptions = {padded: true};
if(ppad) {
xOptions.ppad = yOptions.ppad = ppad;
}
// TODO: text size
var openEnded = serieslen < 2 || (x[0] !== x[serieslen - 1]) || (y[0] !== y[serieslen - 1]);
if(openEnded && (
(fill === 'tozerox') ||
((fill === 'tonextx') && (firstScatter || stackOrientation === 'h'))
)) {
// include zero (tight) and extremes (padded) if fill to zero
// (unless the shape is closed, then it's just filling the shape regardless)
xOptions.tozero = true;
} else if(!(trace.error_y || {}).visible && (
// if no error bars, markers or text, or fill to y=0 remove x padding
(fill === 'tonexty' || fill === 'tozeroy') ||
(!subTypes.hasMarkers(trace) && !subTypes.hasText(trace))
)) {
xOptions.padded = false;
xOptions.ppad = 0;
}
if(openEnded && (
(fill === 'tozeroy') ||
((fill === 'tonexty') && (firstScatter || stackOrientation === 'v'))
)) {
// now check for y - rather different logic, though still mostly padded both ends
// include zero (tight) and extremes (padded) if fill to zero
// (unless the shape is closed, then it's just filling the shape regardless)
yOptions.tozero = true;
} else if(fill === 'tonextx' || fill === 'tozerox') {
// tight y: any x fill
yOptions.padded = false;
}
// N.B. asymmetric splom traces call this with blank {} xa or ya
if(xId) trace._extremes[xId] = Axes.findExtremes(xa, x, xOptions);
if(yId) trace._extremes[yId] = Axes.findExtremes(ya, y, yOptions);
}
function calcMarkerSize(trace, serieslen) {
if(!subTypes.hasMarkers(trace)) return;
// Treat size like x or y arrays --- Run d2c
// this needs to go before ppad computation
var marker = trace.marker;
var sizeref = 1.6 * (trace.marker.sizeref || 1);
var markerTrans;
if(trace.marker.sizemode === 'area') {
markerTrans = function(v) {
return Math.max(Math.sqrt((v || 0) / sizeref), 3);
};
} else {
markerTrans = function(v) {
return Math.max((v || 0) / sizeref, 3);
};
}
if(Lib.isArrayOrTypedArray(marker.size)) {
// I tried auto-type but category and dates dont make much sense.
var ax = {type: 'linear'};
Axes.setConvert(ax);
var s = ax.makeCalcdata(trace.marker, 'size');
var sizeOut = new Array(serieslen);
for(var i = 0; i < serieslen; i++) {
sizeOut[i] = markerTrans(s[i]);
}
return sizeOut;
} else {
return markerTrans(marker.size);
}
}
/**
* mark the first scatter trace for each subplot
* note that scatter and scattergl each get their own first trace
* note also that I'm doing this during calc rather than supplyDefaults
* so I don't need to worry about transforms, but if we ever do
* per-trace calc this will get confused.
*/
function setFirstScatter(fullLayout, trace) {
var group = firstScatterGroup(trace);
var firstScatter = fullLayout._firstScatter;
if(!firstScatter[group]) firstScatter[group] = trace.uid;
}
function firstScatterGroup(trace) {
var stackGroup = trace.stackgroup;
return trace.xaxis + trace.yaxis + trace.type +
(stackGroup ? '-' + stackGroup : '');
}
function getStackOpts(trace, fullLayout, xa, ya) {
var stackGroup = trace.stackgroup;
if(!stackGroup) return;
var stackOpts = fullLayout._scatterStackOpts[xa._id + ya._id][stackGroup];
var stackAx = stackOpts.orientation === 'v' ? ya : xa;
// Allow stacking only on numeric axes
// calc is a little late to be figuring this out, but during supplyDefaults
// we don't know the axis type yet
if(stackAx.type === 'linear' || stackAx.type === 'log') return stackOpts;
}
module.exports = {
calc: calc,
calcMarkerSize: calcMarkerSize,
calcAxisExpansion: calcAxisExpansion,
setFirstScatter: setFirstScatter,
getStackOpts: getStackOpts
};
},{"../../constants/numerical":479,"../../lib":503,"../../plots/cartesian/align_period":551,"../../plots/cartesian/axes":554,"./arrays_to_calcdata":924,"./calc_selection":927,"./colorscale_calc":928,"./subtypes":950,"fast-isnumeric":190}],927:[function(_dereq_,module,exports){
'use strict';
var Lib = _dereq_('../../lib');
module.exports = function calcSelection(cd, trace) {
if(Lib.isArrayOrTypedArray(trace.selectedpoints)) {
Lib.tagSelected(cd, trace);
}
};
},{"../../lib":503}],928:[function(_dereq_,module,exports){
'use strict';
var hasColorscale = _dereq_('../../components/colorscale/helpers').hasColorscale;
var calcColorscale = _dereq_('../../components/colorscale/calc');
var subTypes = _dereq_('./subtypes');
module.exports = function calcMarkerColorscale(gd, trace) {
if(subTypes.hasLines(trace) && hasColorscale(trace, 'line')) {
calcColorscale(gd, trace, {
vals: trace.line.color,
containerStr: 'line',
cLetter: 'c'
});
}
if(subTypes.hasMarkers(trace)) {
if(hasColorscale(trace, 'marker')) {
calcColorscale(gd, trace, {
vals: trace.marker.color,
containerStr: 'marker',
cLetter: 'c'
});
}
if(hasColorscale(trace, 'marker.line')) {
calcColorscale(gd, trace, {
vals: trace.marker.line.color,
containerStr: 'marker.line',
cLetter: 'c'
});
}
}
};
},{"../../components/colorscale/calc":374,"../../components/colorscale/helpers":377,"./subtypes":950}],929:[function(_dereq_,module,exports){
'use strict';
module.exports = {
PTS_LINESONLY: 20,
// fixed parameters of clustering and clipping algorithms
// fraction of clustering tolerance "so close we don't even consider it a new point"
minTolerance: 0.2,
// how fast does clustering tolerance increase as you get away from the visible region
toleranceGrowth: 10,
// number of viewport sizes away from the visible region
// at which we clip all lines to the perimeter
maxScreensAway: 20,
eventDataKeys: []
};
},{}],930:[function(_dereq_,module,exports){
'use strict';
var calc = _dereq_('./calc');
/*
* Scatter stacking & normalization calculations
* runs per subplot, and can handle multiple stacking groups
*/
module.exports = function crossTraceCalc(gd, plotinfo) {
var xa = plotinfo.xaxis;
var ya = plotinfo.yaxis;
var subplot = xa._id + ya._id;
var subplotStackOpts = gd._fullLayout._scatterStackOpts[subplot];
if(!subplotStackOpts) return;
var calcTraces = gd.calcdata;
var i, j, k, i2, cd, cd0, posj, sumj, norm;
var groupOpts, interpolate, groupnorm, posAttr, valAttr;
var hasAnyBlanks;
for(var stackGroup in subplotStackOpts) {
groupOpts = subplotStackOpts[stackGroup];
var indices = groupOpts.traceIndices;
// can get here with no indices if the stack axis is non-numeric
if(!indices.length) continue;
interpolate = groupOpts.stackgaps === 'interpolate';
groupnorm = groupOpts.groupnorm;
if(groupOpts.orientation === 'v') {
posAttr = 'x';
valAttr = 'y';
} else {
posAttr = 'y';
valAttr = 'x';
}
hasAnyBlanks = new Array(indices.length);
for(i = 0; i < hasAnyBlanks.length; i++) {
hasAnyBlanks[i] = false;
}
// Collect the complete set of all positions across ALL traces.
// Start with the first trace, then interleave items from later traces
// as needed.
// Fill in mising items as we go.
cd0 = calcTraces[indices[0]];
var allPositions = new Array(cd0.length);
for(i = 0; i < cd0.length; i++) {
allPositions[i] = cd0[i][posAttr];
}
for(i = 1; i < indices.length; i++) {
cd = calcTraces[indices[i]];
for(j = k = 0; j < cd.length; j++) {
posj = cd[j][posAttr];
for(; posj > allPositions[k] && k < allPositions.length; k++) {
// the current trace is missing a position from some previous trace(s)
insertBlank(cd, j, allPositions[k], i, hasAnyBlanks, interpolate, posAttr);
j++;
}
if(posj !== allPositions[k]) {
// previous trace(s) are missing a position from the current trace
for(i2 = 0; i2 < i; i2++) {
insertBlank(calcTraces[indices[i2]], k, posj, i2, hasAnyBlanks, interpolate, posAttr);
}
allPositions.splice(k, 0, posj);
}
k++;
}
for(; k < allPositions.length; k++) {
insertBlank(cd, j, allPositions[k], i, hasAnyBlanks, interpolate, posAttr);
j++;
}
}
var serieslen = allPositions.length;
// stack (and normalize)!
for(j = 0; j < cd0.length; j++) {
sumj = cd0[j][valAttr] = cd0[j].s;
for(i = 1; i < indices.length; i++) {
cd = calcTraces[indices[i]];
cd[0].trace._rawLength = cd[0].trace._length;
cd[0].trace._length = serieslen;
sumj += cd[j].s;
cd[j][valAttr] = sumj;
}
if(groupnorm) {
norm = ((groupnorm === 'fraction') ? sumj : (sumj / 100)) || 1;
for(i = 0; i < indices.length; i++) {
var cdj = calcTraces[indices[i]][j];
cdj[valAttr] /= norm;
cdj.sNorm = cdj.s / norm;
}
}
}
// autorange
for(i = 0; i < indices.length; i++) {
cd = calcTraces[indices[i]];
var trace = cd[0].trace;
var ppad = calc.calcMarkerSize(trace, trace._rawLength);
var arrayPad = Array.isArray(ppad);
if((ppad && hasAnyBlanks[i]) || arrayPad) {
var ppadRaw = ppad;
ppad = new Array(serieslen);
for(j = 0; j < serieslen; j++) {
ppad[j] = cd[j].gap ? 0 : (arrayPad ? ppadRaw[cd[j].i] : ppadRaw);
}
}
var x = new Array(serieslen);
var y = new Array(serieslen);
for(j = 0; j < serieslen; j++) {
x[j] = cd[j].x;
y[j] = cd[j].y;
}
calc.calcAxisExpansion(gd, trace, xa, ya, x, y, ppad);
// while we're here (in a loop over all traces in the stack)
// record the orientation, so hover can find it easily
cd[0].t.orientation = groupOpts.orientation;
}
}
};
function insertBlank(calcTrace, index, position, traceIndex, hasAnyBlanks, interpolate, posAttr) {
hasAnyBlanks[traceIndex] = true;
var newEntry = {
i: null,
gap: true,
s: 0
};
newEntry[posAttr] = position;
calcTrace.splice(index, 0, newEntry);
// Even if we're not interpolating, if one trace has multiple
// values at the same position and this trace only has one value there,
// we just duplicate that one value rather than insert a zero.
// We also make it look like a real point - because it's ambiguous which
// one really is the real one!
if(index && position === calcTrace[index - 1][posAttr]) {
var prevEntry = calcTrace[index - 1];
newEntry.s = prevEntry.s;
// TODO is it going to cause any problems to have multiple
// calcdata points with the same index?
newEntry.i = prevEntry.i;
newEntry.gap = prevEntry.gap;
} else if(interpolate) {
newEntry.s = getInterp(calcTrace, index, position, posAttr);
}
if(!index) {
// t and trace need to stay on the first cd entry
calcTrace[0].t = calcTrace[1].t;
calcTrace[0].trace = calcTrace[1].trace;
delete calcTrace[1].t;
delete calcTrace[1].trace;
}
}
function getInterp(calcTrace, index, position, posAttr) {
var pt0 = calcTrace[index - 1];
var pt1 = calcTrace[index + 1];
if(!pt1) return pt0.s;
if(!pt0) return pt1.s;
return pt0.s + (pt1.s - pt0.s) * (position - pt0[posAttr]) / (pt1[posAttr] - pt0[posAttr]);
}
},{"./calc":926}],931:[function(_dereq_,module,exports){
'use strict';
// remove opacity for any trace that has a fill or is filled to
module.exports = function crossTraceDefaults(fullData) {
for(var i = 0; i < fullData.length; i++) {
var tracei = fullData[i];
if(tracei.type !== 'scatter') continue;
var filli = tracei.fill;
if(filli === 'none' || filli === 'toself') continue;
tracei.opacity = undefined;
if(filli === 'tonexty' || filli === 'tonextx') {
for(var j = i - 1; j >= 0; j--) {
var tracej = fullData[j];
if((tracej.type === 'scatter') &&
(tracej.xaxis === tracei.xaxis) &&
(tracej.yaxis === tracei.yaxis)) {
tracej.opacity = undefined;
break;
}
}
}
}
};
},{}],932:[function(_dereq_,module,exports){
'use strict';
var Lib = _dereq_('../../lib');
var Registry = _dereq_('../../registry');
var attributes = _dereq_('./attributes');
var constants = _dereq_('./constants');
var subTypes = _dereq_('./subtypes');
var handleXYDefaults = _dereq_('./xy_defaults');
var handlePeriodDefaults = _dereq_('./period_defaults');
var handleStackDefaults = _dereq_('./stack_defaults');
var handleMarkerDefaults = _dereq_('./marker_defaults');
var handleLineDefaults = _dereq_('./line_defaults');
var handleLineShapeDefaults = _dereq_('./line_shape_defaults');
var handleTextDefaults = _dereq_('./text_defaults');
var handleFillColorDefaults = _dereq_('./fillcolor_defaults');
module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout) {
function coerce(attr, dflt) {
return Lib.coerce(traceIn, traceOut, attributes, attr, dflt);
}
var len = handleXYDefaults(traceIn, traceOut, layout, coerce);
if(!len) traceOut.visible = false;
if(!traceOut.visible) return;
handlePeriodDefaults(traceIn, traceOut, layout, coerce);
coerce('xhoverformat');
coerce('yhoverformat');
var stackGroupOpts = handleStackDefaults(traceIn, traceOut, layout, coerce);
var defaultMode = !stackGroupOpts && (len < constants.PTS_LINESONLY) ?
'lines+markers' : 'lines';
coerce('text');
coerce('hovertext');
coerce('mode', defaultMode);
if(subTypes.hasLines(traceOut)) {
handleLineDefaults(traceIn, traceOut, defaultColor, layout, coerce);
handleLineShapeDefaults(traceIn, traceOut, coerce);
coerce('connectgaps');
coerce('line.simplify');
}
if(subTypes.hasMarkers(traceOut)) {
handleMarkerDefaults(traceIn, traceOut, defaultColor, layout, coerce, {gradient: true});
}
if(subTypes.hasText(traceOut)) {
coerce('texttemplate');
handleTextDefaults(traceIn, traceOut, layout, coerce);
}
var dfltHoverOn = [];
if(subTypes.hasMarkers(traceOut) || subTypes.hasText(traceOut)) {
coerce('cliponaxis');
coerce('marker.maxdisplayed');
dfltHoverOn.push('points');
}
// It's possible for this default to be changed by a later trace.
// We handle that case in some hacky code inside handleStackDefaults.
coerce('fill', stackGroupOpts ? stackGroupOpts.fillDflt : 'none');
if(traceOut.fill !== 'none') {
handleFillColorDefaults(traceIn, traceOut, defaultColor, coerce);
if(!subTypes.hasLines(traceOut)) handleLineShapeDefaults(traceIn, traceOut, coerce);
}
var lineColor = (traceOut.line || {}).color;
var markerColor = (traceOut.marker || {}).color;
if(traceOut.fill === 'tonext' || traceOut.fill === 'toself') {
dfltHoverOn.push('fills');
}
coerce('hoveron', dfltHoverOn.join('+') || 'points');
if(traceOut.hoveron !== 'fills') coerce('hovertemplate');
var errorBarsSupplyDefaults = Registry.getComponentMethod('errorbars', 'supplyDefaults');
errorBarsSupplyDefaults(traceIn, traceOut, lineColor || markerColor || defaultColor, {axis: 'y'});
errorBarsSupplyDefaults(traceIn, traceOut, lineColor || markerColor || defaultColor, {axis: 'x', inherit: 'y'});
Lib.coerceSelectionMarkerOpacity(traceOut, coerce);
};
},{"../../lib":503,"../../registry":638,"./attributes":925,"./constants":929,"./fillcolor_defaults":933,"./line_defaults":938,"./line_shape_defaults":940,"./marker_defaults":944,"./period_defaults":945,"./stack_defaults":948,"./subtypes":950,"./text_defaults":951,"./xy_defaults":952}],933:[function(_dereq_,module,exports){
'use strict';
var Color = _dereq_('../../components/color');
var isArrayOrTypedArray = _dereq_('../../lib').isArrayOrTypedArray;
module.exports = function fillColorDefaults(traceIn, traceOut, defaultColor, coerce) {
var inheritColorFromMarker = false;
if(traceOut.marker) {
// don't try to inherit a color array
var markerColor = traceOut.marker.color;
var markerLineColor = (traceOut.marker.line || {}).color;
if(markerColor && !isArrayOrTypedArray(markerColor)) {
inheritColorFromMarker = markerColor;
} else if(markerLineColor && !isArrayOrTypedArray(markerLineColor)) {
inheritColorFromMarker = markerLineColor;
}
}
coerce('fillcolor', Color.addOpacity(
(traceOut.line || {}).color ||
inheritColorFromMarker ||
defaultColor, 0.5
));
};
},{"../../components/color":366,"../../lib":503}],934:[function(_dereq_,module,exports){
'use strict';
var Axes = _dereq_('../../plots/cartesian/axes');
module.exports = function formatLabels(cdi, trace, fullLayout) {
var labels = {};
var mockGd = {_fullLayout: fullLayout};
var xa = Axes.getFromTrace(mockGd, trace, 'x');
var ya = Axes.getFromTrace(mockGd, trace, 'y');
labels.xLabel = Axes.tickText(xa, xa.c2l(cdi.x), true).text;
labels.yLabel = Axes.tickText(ya, ya.c2l(cdi.y), true).text;
return labels;
};
},{"../../plots/cartesian/axes":554}],935:[function(_dereq_,module,exports){
'use strict';
var Color = _dereq_('../../components/color');
var subtypes = _dereq_('./subtypes');
module.exports = function getTraceColor(trace, di) {
var lc, tc;
// TODO: text modes
if(trace.mode === 'lines') {
lc = trace.line.color;
return (lc && Color.opacity(lc)) ?
lc : trace.fillcolor;
} else if(trace.mode === 'none') {
return trace.fill ? trace.fillcolor : '';
} else {
var mc = di.mcc || (trace.marker || {}).color;
var mlc = di.mlcc || ((trace.marker || {}).line || {}).color;
tc = (mc && Color.opacity(mc)) ? mc :
(mlc && Color.opacity(mlc) &&
(di.mlw || ((trace.marker || {}).line || {}).width)) ? mlc : '';
if(tc) {
// make sure the points aren't TOO transparent
if(Color.opacity(tc) < 0.3) {
return Color.addOpacity(tc, 0.3);
} else return tc;
} else {
lc = (trace.line || {}).color;
return (lc && Color.opacity(lc) &&
subtypes.hasLines(trace) && trace.line.width) ?
lc : trace.fillcolor;
}
}
};
},{"../../components/color":366,"./subtypes":950}],936:[function(_dereq_,module,exports){
'use strict';
var Lib = _dereq_('../../lib');
var Fx = _dereq_('../../components/fx');
var Registry = _dereq_('../../registry');
var getTraceColor = _dereq_('./get_trace_color');
var Color = _dereq_('../../components/color');
var fillText = Lib.fillText;
module.exports = function hoverPoints(pointData, xval, yval, hovermode) {
var cd = pointData.cd;
var trace = cd[0].trace;
var xa = pointData.xa;
var ya = pointData.ya;
var xpx = xa.c2p(xval);
var ypx = ya.c2p(yval);
var pt = [xpx, ypx];
var hoveron = trace.hoveron || '';
var minRad = (trace.mode.indexOf('markers') !== -1) ? 3 : 0.5;
var xPeriod = !!trace.xperiodalignment;
var yPeriod = !!trace.yperiodalignment;
// look for points to hover on first, then take fills only if we
// didn't find a point
if(hoveron.indexOf('points') !== -1) {
// dx and dy are used in compare modes - here we want to always
// prioritize the closest data point, at least as long as markers are
// the same size or nonexistent, but still try to prioritize small markers too.
var dx = function(di) {
if(xPeriod) {
var x0 = xa.c2p(di.xStart);
var x1 = xa.c2p(di.xEnd);
return (
xpx >= Math.min(x0, x1) &&
xpx <= Math.max(x0, x1)
) ? 0 : Infinity;
}
var rad = Math.max(3, di.mrc || 0);
var kink = 1 - 1 / rad;
var dxRaw = Math.abs(xa.c2p(di.x) - xpx);
return (dxRaw < rad) ? (kink * dxRaw / rad) : (dxRaw - rad + kink);
};
var dy = function(di) {
if(yPeriod) {
var y0 = ya.c2p(di.yStart);
var y1 = ya.c2p(di.yEnd);
return (
ypx >= Math.min(y0, y1) &&
ypx <= Math.max(y0, y1)
) ? 0 : Infinity;
}
var rad = Math.max(3, di.mrc || 0);
var kink = 1 - 1 / rad;
var dyRaw = Math.abs(ya.c2p(di.y) - ypx);
return (dyRaw < rad) ? (kink * dyRaw / rad) : (dyRaw - rad + kink);
};
// scatter points: d.mrc is the calculated marker radius
// adjust the distance so if you're inside the marker it
// always will show up regardless of point size, but
// prioritize smaller points
var dxy = function(di) {
var rad = Math.max(minRad, di.mrc || 0);
var dx = xa.c2p(di.x) - xpx;
var dy = ya.c2p(di.y) - ypx;
return Math.max(Math.sqrt(dx * dx + dy * dy) - rad, 1 - minRad / rad);
};
var distfn = Fx.getDistanceFunction(hovermode, dx, dy, dxy);
Fx.getClosest(cd, distfn, pointData);
// skip the rest (for this trace) if we didn't find a close point
if(pointData.index !== false) {
// the closest data point
var di = cd[pointData.index];
var xc = xa.c2p(di.x, true);
var yc = ya.c2p(di.y, true);
var rad = di.mrc || 1;
// now we're done using the whole `calcdata` array, replace the
// index with the original index (in case of inserted point from
// stacked area)
pointData.index = di.i;
var orientation = cd[0].t.orientation;
// TODO: for scatter and bar, option to show (sub)totals and
// raw data? Currently stacked and/or normalized bars just show
// the normalized individual sizes, so that's what I'm doing here
// for now.
var sizeVal = orientation && (di.sNorm || di.s);
var xLabelVal = (orientation === 'h') ? sizeVal : di.orig_x !== undefined ? di.orig_x : di.x;
var yLabelVal = (orientation === 'v') ? sizeVal : di.orig_y !== undefined ? di.orig_y : di.y;
Lib.extendFlat(pointData, {
color: getTraceColor(trace, di),
x0: xc - rad,
x1: xc + rad,
xLabelVal: xLabelVal,
y0: yc - rad,
y1: yc + rad,
yLabelVal: yLabelVal,
spikeDistance: dxy(di),
hovertemplate: trace.hovertemplate
});
fillText(di, trace, pointData);
Registry.getComponentMethod('errorbars', 'hoverInfo')(di, trace, pointData);
return [pointData];
}
}
// even if hoveron is 'fills', only use it if we have polygons too
if(hoveron.indexOf('fills') !== -1 && trace._polygons) {
var polygons = trace._polygons;
var polygonsIn = [];
var inside = false;
var xmin = Infinity;
var xmax = -Infinity;
var ymin = Infinity;
var ymax = -Infinity;
var i, j, polygon, pts, xCross, x0, x1, y0, y1;
for(i = 0; i < polygons.length; i++) {
polygon = polygons[i];
// TODO: this is not going to work right for curved edges, it will
// act as though they're straight. That's probably going to need
// the elements themselves to capture the events. Worth it?
if(polygon.contains(pt)) {
inside = !inside;
// TODO: need better than just the overall bounding box
polygonsIn.push(polygon);
ymin = Math.min(ymin, polygon.ymin);
ymax = Math.max(ymax, polygon.ymax);
}
}
if(inside) {
// constrain ymin/max to the visible plot, so the label goes
// at the middle of the piece you can see
ymin = Math.max(ymin, 0);
ymax = Math.min(ymax, ya._length);
// find the overall left-most and right-most points of the
// polygon(s) we're inside at their combined vertical midpoint.
// This is where we will draw the hover label.
// Note that this might not be the vertical midpoint of the
// whole trace, if it's disjoint.
var yAvg = (ymin + ymax) / 2;
for(i = 0; i < polygonsIn.length; i++) {
pts = polygonsIn[i].pts;
for(j = 1; j < pts.length; j++) {
y0 = pts[j - 1][1];
y1 = pts[j][1];
if((y0 > yAvg) !== (y1 >= yAvg)) {
x0 = pts[j - 1][0];
x1 = pts[j][0];
if(y1 - y0) {
xCross = x0 + (x1 - x0) * (yAvg - y0) / (y1 - y0);
xmin = Math.min(xmin, xCross);
xmax = Math.max(xmax, xCross);
}
}
}
}
// constrain xmin/max to the visible plot now too
xmin = Math.max(xmin, 0);
xmax = Math.min(xmax, xa._length);
// get only fill or line color for the hover color
var color = Color.defaultLine;
if(Color.opacity(trace.fillcolor)) color = trace.fillcolor;
else if(Color.opacity((trace.line || {}).color)) {
color = trace.line.color;
}
Lib.extendFlat(pointData, {
// never let a 2D override 1D type as closest point
// also: no spikeDistance, it's not allowed for fills
distance: pointData.maxHoverDistance,
x0: xmin,
x1: xmax,
y0: yAvg,
y1: yAvg,
color: color,
hovertemplate: false
});
delete pointData.index;
if(trace.text && !Array.isArray(trace.text)) {
pointData.text = String(trace.text);
} else pointData.text = trace.name;
return [pointData];
}
}
};
},{"../../components/color":366,"../../components/fx":406,"../../lib":503,"../../registry":638,"./get_trace_color":935}],937:[function(_dereq_,module,exports){
'use strict';
var subtypes = _dereq_('./subtypes');
module.exports = {
hasLines: subtypes.hasLines,
hasMarkers: subtypes.hasMarkers,
hasText: subtypes.hasText,
isBubble: subtypes.isBubble,
attributes: _dereq_('./attributes'),
supplyDefaults: _dereq_('./defaults'),
crossTraceDefaults: _dereq_('./cross_trace_defaults'),
calc: _dereq_('./calc').calc,
crossTraceCalc: _dereq_('./cross_trace_calc'),
arraysToCalcdata: _dereq_('./arrays_to_calcdata'),
plot: _dereq_('./plot'),
colorbar: _dereq_('./marker_colorbar'),
formatLabels: _dereq_('./format_labels'),
style: _dereq_('./style').style,
styleOnSelect: _dereq_('./style').styleOnSelect,
hoverPoints: _dereq_('./hover'),
selectPoints: _dereq_('./select'),
animatable: true,
moduleType: 'trace',
name: 'scatter',
basePlotModule: _dereq_('../../plots/cartesian'),
categories: [
'cartesian', 'svg', 'symbols', 'errorBarsOK', 'showLegend', 'scatter-like',
'zoomScale'
],
meta: {
}
};
},{"../../plots/cartesian":568,"./arrays_to_calcdata":924,"./attributes":925,"./calc":926,"./cross_trace_calc":930,"./cross_trace_defaults":931,"./defaults":932,"./format_labels":934,"./hover":936,"./marker_colorbar":943,"./plot":946,"./select":947,"./style":949,"./subtypes":950}],938:[function(_dereq_,module,exports){
'use strict';
var isArrayOrTypedArray = _dereq_('../../lib').isArrayOrTypedArray;
var hasColorscale = _dereq_('../../components/colorscale/helpers').hasColorscale;
var colorscaleDefaults = _dereq_('../../components/colorscale/defaults');
module.exports = function lineDefaults(traceIn, traceOut, defaultColor, layout, coerce, opts) {
var markerColor = (traceIn.marker || {}).color;
coerce('line.color', defaultColor);
if(hasColorscale(traceIn, 'line')) {
colorscaleDefaults(traceIn, traceOut, layout, coerce, {prefix: 'line.', cLetter: 'c'});
} else {
var lineColorDflt = (isArrayOrTypedArray(markerColor) ? false : markerColor) || defaultColor;
coerce('line.color', lineColorDflt);
}
coerce('line.width');
if(!(opts || {}).noDash) coerce('line.dash');
};
},{"../../components/colorscale/defaults":376,"../../components/colorscale/helpers":377,"../../lib":503}],939:[function(_dereq_,module,exports){
'use strict';
var numConstants = _dereq_('../../constants/numerical');
var BADNUM = numConstants.BADNUM;
var LOG_CLIP = numConstants.LOG_CLIP;
var LOG_CLIP_PLUS = LOG_CLIP + 0.5;
var LOG_CLIP_MINUS = LOG_CLIP - 0.5;
var Lib = _dereq_('../../lib');
var segmentsIntersect = Lib.segmentsIntersect;
var constrain = Lib.constrain;
var constants = _dereq_('./constants');
module.exports = function linePoints(d, opts) {
var xa = opts.xaxis;
var ya = opts.yaxis;
var xLog = xa.type === 'log';
var yLog = ya.type === 'log';
var xLen = xa._length;
var yLen = ya._length;
var connectGaps = opts.connectGaps;
var baseTolerance = opts.baseTolerance;
var shape = opts.shape;
var linear = shape === 'linear';
var fill = opts.fill && opts.fill !== 'none';
var segments = [];
var minTolerance = constants.minTolerance;
var len = d.length;
var pts = new Array(len);
var pti = 0;
var i;
// pt variables are pixel coordinates [x,y] of one point
// these four are the outputs of clustering on a line
var clusterStartPt, clusterEndPt, clusterHighPt, clusterLowPt;
// "this" is the next point we're considering adding to the cluster
var thisPt;
// did we encounter the high point first, then a low point, or vice versa?
var clusterHighFirst;
// the first two points in the cluster determine its unit vector
// so the second is always in the "High" direction
var clusterUnitVector;
// the pixel delta from clusterStartPt
var thisVector;
// val variables are (signed) pixel distances along the cluster vector
var clusterRefDist, clusterHighVal, clusterLowVal, thisVal;
// deviation variables are (signed) pixel distances normal to the cluster vector
var clusterMinDeviation, clusterMaxDeviation, thisDeviation;
// turn one calcdata point into pixel coordinates
function getPt(index) {
var di = d[index];
if(!di) return false;
var x = opts.linearized ? xa.l2p(di.x) : xa.c2p(di.x);
var y = opts.linearized ? ya.l2p(di.y) : ya.c2p(di.y);
// if non-positive log values, set them VERY far off-screen
// so the line looks essentially straight from the previous point.
if(x === BADNUM) {
if(xLog) x = xa.c2p(di.x, true);
if(x === BADNUM) return false;
// If BOTH were bad log values, make the line follow a constant
// exponent rather than a constant slope
if(yLog && y === BADNUM) {
x *= Math.abs(xa._m * yLen * (xa._m > 0 ? LOG_CLIP_PLUS : LOG_CLIP_MINUS) /
(ya._m * xLen * (ya._m > 0 ? LOG_CLIP_PLUS : LOG_CLIP_MINUS)));
}
x *= 1000;
}
if(y === BADNUM) {
if(yLog) y = ya.c2p(di.y, true);
if(y === BADNUM) return false;
y *= 1000;
}
return [x, y];
}
function crossesViewport(xFrac0, yFrac0, xFrac1, yFrac1) {
var dx = xFrac1 - xFrac0;
var dy = yFrac1 - yFrac0;
var dx0 = 0.5 - xFrac0;
var dy0 = 0.5 - yFrac0;
var norm2 = dx * dx + dy * dy;
var dot = dx * dx0 + dy * dy0;
if(dot > 0 && dot < norm2) {
var cross = dx0 * dy - dy0 * dx;
if(cross * cross < norm2) return true;
}
}
var latestXFrac, latestYFrac;
// if we're off-screen, increase tolerance over baseTolerance
function getTolerance(pt, nextPt) {
var xFrac = pt[0] / xLen;
var yFrac = pt[1] / yLen;
var offScreenFraction = Math.max(0, -xFrac, xFrac - 1, -yFrac, yFrac - 1);
if(offScreenFraction && (latestXFrac !== undefined) &&
crossesViewport(xFrac, yFrac, latestXFrac, latestYFrac)
) {
offScreenFraction = 0;
}
if(offScreenFraction && nextPt &&
crossesViewport(xFrac, yFrac, nextPt[0] / xLen, nextPt[1] / yLen)
) {
offScreenFraction = 0;
}
return (1 + constants.toleranceGrowth * offScreenFraction) * baseTolerance;
}
function ptDist(pt1, pt2) {
var dx = pt1[0] - pt2[0];
var dy = pt1[1] - pt2[1];
return Math.sqrt(dx * dx + dy * dy);
}
// last bit of filtering: clip paths that are VERY far off-screen
// so we don't get near the browser's hard limit (+/- 2^29 px in Chrome and FF)
var maxScreensAway = constants.maxScreensAway;
// find the intersections between the segment from pt1 to pt2
// and the large rectangle maxScreensAway around the viewport
// if one of pt1 and pt2 is inside and the other outside, there
// will be only one intersection.
// if both are outside there will be 0 or 2 intersections
// (or 1 if it's right at a corner - we'll treat that like 0)
// returns an array of intersection pts
var xEdge0 = -xLen * maxScreensAway;
var xEdge1 = xLen * (1 + maxScreensAway);
var yEdge0 = -yLen * maxScreensAway;
var yEdge1 = yLen * (1 + maxScreensAway);
var edges = [
[xEdge0, yEdge0, xEdge1, yEdge0],
[xEdge1, yEdge0, xEdge1, yEdge1],
[xEdge1, yEdge1, xEdge0, yEdge1],
[xEdge0, yEdge1, xEdge0, yEdge0]
];
var xEdge, yEdge, lastXEdge, lastYEdge, lastFarPt, edgePt;
// for linear line shape, edge intersections should be linearly interpolated
// spline uses this too, which isn't precisely correct but is actually pretty
// good, because Catmull-Rom weights far-away points less in creating the curvature
function getLinearEdgeIntersections(pt1, pt2) {
var out = [];
var ptCount = 0;
for(var i = 0; i < 4; i++) {
var edge = edges[i];
var ptInt = segmentsIntersect(
pt1[0], pt1[1], pt2[0], pt2[1],
edge[0], edge[1], edge[2], edge[3]
);
if(ptInt && (!ptCount ||
Math.abs(ptInt.x - out[0][0]) > 1 ||
Math.abs(ptInt.y - out[0][1]) > 1
)) {
ptInt = [ptInt.x, ptInt.y];
// if we have 2 intersections, make sure the closest one to pt1 comes first
if(ptCount && ptDist(ptInt, pt1) < ptDist(out[0], pt1)) out.unshift(ptInt);
else out.push(ptInt);
ptCount++;
}
}
return out;
}
function onlyConstrainedPoint(pt) {
if(pt[0] < xEdge0 || pt[0] > xEdge1 || pt[1] < yEdge0 || pt[1] > yEdge1) {
return [constrain(pt[0], xEdge0, xEdge1), constrain(pt[1], yEdge0, yEdge1)];
}
}
function sameEdge(pt1, pt2) {
if(pt1[0] === pt2[0] && (pt1[0] === xEdge0 || pt1[0] === xEdge1)) return true;
if(pt1[1] === pt2[1] && (pt1[1] === yEdge0 || pt1[1] === yEdge1)) return true;
}
// for line shapes hv and vh, movement in the two dimensions is decoupled,
// so all we need to do is constrain each dimension independently
function getHVEdgeIntersections(pt1, pt2) {
var out = [];
var ptInt1 = onlyConstrainedPoint(pt1);
var ptInt2 = onlyConstrainedPoint(pt2);
if(ptInt1 && ptInt2 && sameEdge(ptInt1, ptInt2)) return out;
if(ptInt1) out.push(ptInt1);
if(ptInt2) out.push(ptInt2);
return out;
}
// hvh and vhv we sometimes have to move one of the intersection points
// out BEYOND the clipping rect, by a maximum of a factor of 2, so that
// the midpoint line is drawn in the right place
function getABAEdgeIntersections(dim, limit0, limit1) {
return function(pt1, pt2) {
var ptInt1 = onlyConstrainedPoint(pt1);
var ptInt2 = onlyConstrainedPoint(pt2);
var out = [];
if(ptInt1 && ptInt2 && sameEdge(ptInt1, ptInt2)) return out;
if(ptInt1) out.push(ptInt1);
if(ptInt2) out.push(ptInt2);
var midShift = 2 * Lib.constrain((pt1[dim] + pt2[dim]) / 2, limit0, limit1) -
((ptInt1 || pt1)[dim] + (ptInt2 || pt2)[dim]);
if(midShift) {
var ptToAlter;
if(ptInt1 && ptInt2) {
ptToAlter = (midShift > 0 === ptInt1[dim] > ptInt2[dim]) ? ptInt1 : ptInt2;
} else ptToAlter = ptInt1 || ptInt2;
ptToAlter[dim] += midShift;
}
return out;
};
}
var getEdgeIntersections;
if(shape === 'linear' || shape === 'spline') {
getEdgeIntersections = getLinearEdgeIntersections;
} else if(shape === 'hv' || shape === 'vh') {
getEdgeIntersections = getHVEdgeIntersections;
} else if(shape === 'hvh') getEdgeIntersections = getABAEdgeIntersections(0, xEdge0, xEdge1);
else if(shape === 'vhv') getEdgeIntersections = getABAEdgeIntersections(1, yEdge0, yEdge1);
// a segment pt1->pt2 entirely outside the nearby region:
// find the corner it gets closest to touching
function getClosestCorner(pt1, pt2) {
var dx = pt2[0] - pt1[0];
var m = (pt2[1] - pt1[1]) / dx;
var b = (pt1[1] * pt2[0] - pt2[1] * pt1[0]) / dx;
if(b > 0) return [m > 0 ? xEdge0 : xEdge1, yEdge1];
else return [m > 0 ? xEdge1 : xEdge0, yEdge0];
}
function updateEdge(pt) {
var x = pt[0];
var y = pt[1];
var xSame = x === pts[pti - 1][0];
var ySame = y === pts[pti - 1][1];
// duplicate point?
if(xSame && ySame) return;
if(pti > 1) {
// backtracking along an edge?
var xSame2 = x === pts[pti - 2][0];
var ySame2 = y === pts[pti - 2][1];
if(xSame && (x === xEdge0 || x === xEdge1) && xSame2) {
if(ySame2) pti--; // backtracking exactly - drop prev pt and don't add
else pts[pti - 1] = pt; // not exact: replace the prev pt
} else if(ySame && (y === yEdge0 || y === yEdge1) && ySame2) {
if(xSame2) pti--;
else pts[pti - 1] = pt;
} else pts[pti++] = pt;
} else pts[pti++] = pt;
}
function updateEdgesForReentry(pt) {
// if we're outside the nearby region and going back in,
// we may need to loop around a corner point
if(pts[pti - 1][0] !== pt[0] && pts[pti - 1][1] !== pt[1]) {
updateEdge([lastXEdge, lastYEdge]);
}
updateEdge(pt);
lastFarPt = null;
lastXEdge = lastYEdge = 0;
}
function addPt(pt) {
latestXFrac = pt[0] / xLen;
latestYFrac = pt[1] / yLen;
// Are we more than maxScreensAway off-screen any direction?
// if so, clip to this box, but in such a way that on-screen
// drawing is unchanged
xEdge = (pt[0] < xEdge0) ? xEdge0 : (pt[0] > xEdge1) ? xEdge1 : 0;
yEdge = (pt[1] < yEdge0) ? yEdge0 : (pt[1] > yEdge1) ? yEdge1 : 0;
if(xEdge || yEdge) {
if(!pti) {
// to get fills right - if first point is far, push it toward the
// screen in whichever direction(s) are far
pts[pti++] = [xEdge || pt[0], yEdge || pt[1]];
} else if(lastFarPt) {
// both this point and the last are outside the nearby region
// check if we're crossing the nearby region
var intersections = getEdgeIntersections(lastFarPt, pt);
if(intersections.length > 1) {
updateEdgesForReentry(intersections[0]);
pts[pti++] = intersections[1];
}
} else {
// we're leaving the nearby region - add the point where we left it
edgePt = getEdgeIntersections(pts[pti - 1], pt)[0];
pts[pti++] = edgePt;
}
var lastPt = pts[pti - 1];
if(xEdge && yEdge && (lastPt[0] !== xEdge || lastPt[1] !== yEdge)) {
// we've gone out beyond a new corner: add the corner too
// so that the next point will take the right winding
if(lastFarPt) {
if(lastXEdge !== xEdge && lastYEdge !== yEdge) {
if(lastXEdge && lastYEdge) {
// we've gone around to an opposite corner - we
// need to add the correct extra corner
// in order to get the right winding
updateEdge(getClosestCorner(lastFarPt, pt));
} else {
// we're coming from a far edge - the extra corner
// we need is determined uniquely by the sectors
updateEdge([lastXEdge || xEdge, lastYEdge || yEdge]);
}
} else if(lastXEdge && lastYEdge) {
updateEdge([lastXEdge, lastYEdge]);
}
}
updateEdge([xEdge, yEdge]);
} else if((lastXEdge - xEdge) && (lastYEdge - yEdge)) {
// we're coming from an edge or far corner to an edge - again the
// extra corner we need is uniquely determined by the sectors
updateEdge([xEdge || lastXEdge, yEdge || lastYEdge]);
}
lastFarPt = pt;
lastXEdge = xEdge;
lastYEdge = yEdge;
} else {
if(lastFarPt) {
// this point is in range but the previous wasn't: add its entry pt first
updateEdgesForReentry(getEdgeIntersections(lastFarPt, pt)[0]);
}
pts[pti++] = pt;
}
}
// loop over ALL points in this trace
for(i = 0; i < len; i++) {
clusterStartPt = getPt(i);
if(!clusterStartPt) continue;
pti = 0;
lastFarPt = null;
addPt(clusterStartPt);
// loop over one segment of the trace
for(i++; i < len; i++) {
clusterHighPt = getPt(i);
if(!clusterHighPt) {
if(connectGaps) continue;
else break;
}
// can't decimate if nonlinear line shape
// TODO: we *could* decimate [hv]{2,3} shapes if we restricted clusters to horz or vert again
// but spline would be verrry awkward to decimate
if(!linear || !opts.simplify) {
addPt(clusterHighPt);
continue;
}
var nextPt = getPt(i + 1);
clusterRefDist = ptDist(clusterHighPt, clusterStartPt);
// #3147 - always include the very first and last points for fills
if(!(fill && (pti === 0 || pti === len - 1)) &&
clusterRefDist < getTolerance(clusterHighPt, nextPt) * minTolerance) continue;
clusterUnitVector = [
(clusterHighPt[0] - clusterStartPt[0]) / clusterRefDist,
(clusterHighPt[1] - clusterStartPt[1]) / clusterRefDist
];
clusterLowPt = clusterStartPt;
clusterHighVal = clusterRefDist;
clusterLowVal = clusterMinDeviation = clusterMaxDeviation = 0;
clusterHighFirst = false;
clusterEndPt = clusterHighPt;
// loop over one cluster of points that collapse onto one line
for(i++; i < d.length; i++) {
thisPt = nextPt;
nextPt = getPt(i + 1);
if(!thisPt) {
if(connectGaps) continue;
else break;
}
thisVector = [
thisPt[0] - clusterStartPt[0],
thisPt[1] - clusterStartPt[1]
];
// cross product (or dot with normal to the cluster vector)
thisDeviation = thisVector[0] * clusterUnitVector[1] - thisVector[1] * clusterUnitVector[0];
clusterMinDeviation = Math.min(clusterMinDeviation, thisDeviation);
clusterMaxDeviation = Math.max(clusterMaxDeviation, thisDeviation);
if(clusterMaxDeviation - clusterMinDeviation > getTolerance(thisPt, nextPt)) break;
clusterEndPt = thisPt;
thisVal = thisVector[0] * clusterUnitVector[0] + thisVector[1] * clusterUnitVector[1];
if(thisVal > clusterHighVal) {
clusterHighVal = thisVal;
clusterHighPt = thisPt;
clusterHighFirst = false;
} else if(thisVal < clusterLowVal) {
clusterLowVal = thisVal;
clusterLowPt = thisPt;
clusterHighFirst = true;
}
}
// insert this cluster into pts
// we've already inserted the start pt, now check if we have high and low pts
if(clusterHighFirst) {
addPt(clusterHighPt);
if(clusterEndPt !== clusterLowPt) addPt(clusterLowPt);
} else {
if(clusterLowPt !== clusterStartPt) addPt(clusterLowPt);
if(clusterEndPt !== clusterHighPt) addPt(clusterHighPt);
}
// and finally insert the end pt
addPt(clusterEndPt);
// have we reached the end of this segment?
if(i >= d.length || !thisPt) break;
// otherwise we have an out-of-cluster point to insert as next clusterStartPt
addPt(thisPt);
clusterStartPt = thisPt;
}
// to get fills right - repeat what we did at the start
if(lastFarPt) updateEdge([lastXEdge || lastFarPt[0], lastYEdge || lastFarPt[1]]);
segments.push(pts.slice(0, pti));
}
return segments;
};
},{"../../constants/numerical":479,"../../lib":503,"./constants":929}],940:[function(_dereq_,module,exports){
'use strict';
// common to 'scatter' and 'scatterternary'
module.exports = function handleLineShapeDefaults(traceIn, traceOut, coerce) {
var shape = coerce('line.shape');
if(shape === 'spline') coerce('line.smoothing');
};
},{}],941:[function(_dereq_,module,exports){
'use strict';
var LINKEDFILLS = {tonextx: 1, tonexty: 1, tonext: 1};
module.exports = function linkTraces(gd, plotinfo, cdscatter) {
var trace, i, group, prevtrace, groupIndex;
// first sort traces to keep stacks & filled-together groups together
var groupIndices = {};
var needsSort = false;
var prevGroupIndex = -1;
var nextGroupIndex = 0;
var prevUnstackedGroupIndex = -1;
for(i = 0; i < cdscatter.length; i++) {
trace = cdscatter[i][0].trace;
group = trace.stackgroup || '';
if(group) {
if(group in groupIndices) {
groupIndex = groupIndices[group];
} else {
groupIndex = groupIndices[group] = nextGroupIndex;
nextGroupIndex++;
}
} else if(trace.fill in LINKEDFILLS && prevUnstackedGroupIndex >= 0) {
groupIndex = prevUnstackedGroupIndex;
} else {
groupIndex = prevUnstackedGroupIndex = nextGroupIndex;
nextGroupIndex++;
}
if(groupIndex < prevGroupIndex) needsSort = true;
trace._groupIndex = prevGroupIndex = groupIndex;
}
var cdscatterSorted = cdscatter.slice();
if(needsSort) {
cdscatterSorted.sort(function(a, b) {
var traceA = a[0].trace;
var traceB = b[0].trace;
return (traceA._groupIndex - traceB._groupIndex) ||
(traceA.index - traceB.index);
});
}
// now link traces to each other
var prevtraces = {};
for(i = 0; i < cdscatterSorted.length; i++) {
trace = cdscatterSorted[i][0].trace;
group = trace.stackgroup || '';
// Note: The check which ensures all cdscatter here are for the same axis and
// are either cartesian or scatterternary has been removed. This code assumes
// the passed scattertraces have been filtered to the proper plot types and
// the proper subplots.
if(trace.visible === true) {
trace._nexttrace = null;
if(trace.fill in LINKEDFILLS) {
prevtrace = prevtraces[group];
trace._prevtrace = prevtrace || null;
if(prevtrace) {
prevtrace._nexttrace = trace;
}
}
trace._ownfill = (trace.fill && (
trace.fill.substr(0, 6) === 'tozero' ||
trace.fill === 'toself' ||
(trace.fill.substr(0, 2) === 'to' && !trace._prevtrace)
));
prevtraces[group] = trace;
} else {
trace._prevtrace = trace._nexttrace = trace._ownfill = null;
}
}
return cdscatterSorted;
};
},{}],942:[function(_dereq_,module,exports){
'use strict';
var isNumeric = _dereq_('fast-isnumeric');
// used in the drawing step for 'scatter' and 'scattegeo' and
// in the convert step for 'scatter3d'
module.exports = function makeBubbleSizeFn(trace, factor) {
if(!factor) {
factor = 2;
}
var marker = trace.marker;
var sizeRef = marker.sizeref || 1;
var sizeMin = marker.sizemin || 0;
// for bubble charts, allow scaling the provided value linearly
// and by area or diameter.
// Note this only applies to the array-value sizes
var baseFn = (marker.sizemode === 'area') ?
function(v) { return Math.sqrt(v / sizeRef); } :
function(v) { return v / sizeRef; };
// TODO add support for position/negative bubbles?
// TODO add 'sizeoffset' attribute?
return function(v) {
var baseSize = baseFn(v / factor);
// don't show non-numeric and negative sizes
return (isNumeric(baseSize) && (baseSize > 0)) ?
Math.max(baseSize, sizeMin) :
0;
};
};
},{"fast-isnumeric":190}],943:[function(_dereq_,module,exports){
'use strict';
module.exports = {
container: 'marker',
min: 'cmin',
max: 'cmax'
};
},{}],944:[function(_dereq_,module,exports){
'use strict';
var Color = _dereq_('../../components/color');
var hasColorscale = _dereq_('../../components/colorscale/helpers').hasColorscale;
var colorscaleDefaults = _dereq_('../../components/colorscale/defaults');
var subTypes = _dereq_('./subtypes');
/*
* opts: object of flags to control features not all marker users support
* noLine: caller does not support marker lines
* gradient: caller supports gradients
* noSelect: caller does not support selected/unselected attribute containers
*/
module.exports = function markerDefaults(traceIn, traceOut, defaultColor, layout, coerce, opts) {
var isBubble = subTypes.isBubble(traceIn);
var lineColor = (traceIn.line || {}).color;
var defaultMLC;
opts = opts || {};
// marker.color inherit from line.color (even if line.color is an array)
if(lineColor) defaultColor = lineColor;
coerce('marker.symbol');
coerce('marker.opacity', isBubble ? 0.7 : 1);
coerce('marker.size');
coerce('marker.color', defaultColor);
if(hasColorscale(traceIn, 'marker')) {
colorscaleDefaults(traceIn, traceOut, layout, coerce, {prefix: 'marker.', cLetter: 'c'});
}
if(!opts.noSelect) {
coerce('selected.marker.color');
coerce('unselected.marker.color');
coerce('selected.marker.size');
coerce('unselected.marker.size');
}
if(!opts.noLine) {
// if there's a line with a different color than the marker, use
// that line color as the default marker line color
// (except when it's an array)
// mostly this is for transparent markers to behave nicely
if(lineColor && !Array.isArray(lineColor) && (traceOut.marker.color !== lineColor)) {
defaultMLC = lineColor;
} else if(isBubble) defaultMLC = Color.background;
else defaultMLC = Color.defaultLine;
coerce('marker.line.color', defaultMLC);
if(hasColorscale(traceIn, 'marker.line')) {
colorscaleDefaults(traceIn, traceOut, layout, coerce, {prefix: 'marker.line.', cLetter: 'c'});
}
coerce('marker.line.width', isBubble ? 1 : 0);
}
if(isBubble) {
coerce('marker.sizeref');
coerce('marker.sizemin');
coerce('marker.sizemode');
}
if(opts.gradient) {
var gradientType = coerce('marker.gradient.type');
if(gradientType !== 'none') {
coerce('marker.gradient.color');
}
}
};
},{"../../components/color":366,"../../components/colorscale/defaults":376,"../../components/colorscale/helpers":377,"./subtypes":950}],945:[function(_dereq_,module,exports){
'use strict';
var dateTick0 = _dereq_('../../lib').dateTick0;
var numConstants = _dereq_('../../constants/numerical');
var ONEWEEK = numConstants.ONEWEEK;
function getPeriod0Dflt(period, calendar) {
if(period % ONEWEEK === 0) {
return dateTick0(calendar, 1); // Sunday
}
return dateTick0(calendar, 0);
}
module.exports = function handlePeriodDefaults(traceIn, traceOut, layout, coerce, opts) {
if(!opts) {
opts = {
x: true,
y: true
};
}
if(opts.x) {
var xperiod = coerce('xperiod');
if(xperiod) {
coerce('xperiod0', getPeriod0Dflt(xperiod, traceOut.xcalendar));
coerce('xperiodalignment');
}
}
if(opts.y) {
var yperiod = coerce('yperiod');
if(yperiod) {
coerce('yperiod0', getPeriod0Dflt(yperiod, traceOut.ycalendar));
coerce('yperiodalignment');
}
}
};
},{"../../constants/numerical":479,"../../lib":503}],946:[function(_dereq_,module,exports){
'use strict';
var d3 = _dereq_('@plotly/d3');
var Registry = _dereq_('../../registry');
var Lib = _dereq_('../../lib');
var ensureSingle = Lib.ensureSingle;
var identity = Lib.identity;
var Drawing = _dereq_('../../components/drawing');
var subTypes = _dereq_('./subtypes');
var linePoints = _dereq_('./line_points');
var linkTraces = _dereq_('./link_traces');
var polygonTester = _dereq_('../../lib/polygon').tester;
module.exports = function plot(gd, plotinfo, cdscatter, scatterLayer, transitionOpts, makeOnCompleteCallback) {
var join, onComplete;
// If transition config is provided, then it is only a partial replot and traces not
// updated are removed.
var isFullReplot = !transitionOpts;
var hasTransition = !!transitionOpts && transitionOpts.duration > 0;
// Link traces so the z-order of fill layers is correct
var cdscatterSorted = linkTraces(gd, plotinfo, cdscatter);
join = scatterLayer.selectAll('g.trace')
.data(cdscatterSorted, function(d) { return d[0].trace.uid; });
// Append new traces:
join.enter().append('g')
.attr('class', function(d) {
return 'trace scatter trace' + d[0].trace.uid;
})
.style('stroke-miterlimit', 2);
join.order();
createFills(gd, join, plotinfo);
if(hasTransition) {
if(makeOnCompleteCallback) {
// If it was passed a callback to register completion, make a callback. If
// this is created, then it must be executed on completion, otherwise the
// pos-transition redraw will not execute:
onComplete = makeOnCompleteCallback();
}
var transition = d3.transition()
.duration(transitionOpts.duration)
.ease(transitionOpts.easing)
.each('end', function() {
onComplete && onComplete();
})
.each('interrupt', function() {
onComplete && onComplete();
});
transition.each(function() {
// Must run the selection again since otherwise enters/updates get grouped together
// and these get executed out of order. Except we need them in order!
scatterLayer.selectAll('g.trace').each(function(d, i) {
plotOne(gd, i, plotinfo, d, cdscatterSorted, this, transitionOpts);
});
});
} else {
join.each(function(d, i) {
plotOne(gd, i, plotinfo, d, cdscatterSorted, this, transitionOpts);
});
}
if(isFullReplot) {
join.exit().remove();
}
// remove paths that didn't get used
scatterLayer.selectAll('path:not([d])').remove();
};
function createFills(gd, traceJoin, plotinfo) {
traceJoin.each(function(d) {
var fills = ensureSingle(d3.select(this), 'g', 'fills');
Drawing.setClipUrl(fills, plotinfo.layerClipId, gd);
var trace = d[0].trace;
var fillData = [];
if(trace._ownfill) fillData.push('_ownFill');
if(trace._nexttrace) fillData.push('_nextFill');
var fillJoin = fills.selectAll('g').data(fillData, identity);
fillJoin.enter().append('g');
fillJoin.exit()
.each(function(d) { trace[d] = null; })
.remove();
fillJoin.order().each(function(d) {
// make a path element inside the fill group, just so
// we can give it its own data later on and the group can
// keep its simple '_*Fill' data
trace[d] = ensureSingle(d3.select(this), 'path', 'js-fill');
});
});
}
function plotOne(gd, idx, plotinfo, cdscatter, cdscatterAll, element, transitionOpts) {
var i;
// Since this has been reorganized and we're executing this on individual traces,
// we need to pass it the full list of cdscatter as well as this trace's index (idx)
// since it does an internal n^2 loop over comparisons with other traces:
selectMarkers(gd, idx, plotinfo, cdscatter, cdscatterAll);
var hasTransition = !!transitionOpts && transitionOpts.duration > 0;
function transition(selection) {
return hasTransition ? selection.transition() : selection;
}
var xa = plotinfo.xaxis;
var ya = plotinfo.yaxis;
var trace = cdscatter[0].trace;
var line = trace.line;
var tr = d3.select(element);
var errorBarGroup = ensureSingle(tr, 'g', 'errorbars');
var lines = ensureSingle(tr, 'g', 'lines');
var points = ensureSingle(tr, 'g', 'points');
var text = ensureSingle(tr, 'g', 'text');
// error bars are at the bottom
Registry.getComponentMethod('errorbars', 'plot')(gd, errorBarGroup, plotinfo, transitionOpts);
if(trace.visible !== true) return;
transition(tr).style('opacity', trace.opacity);
// BUILD LINES AND FILLS
var ownFillEl3, tonext;
var ownFillDir = trace.fill.charAt(trace.fill.length - 1);
if(ownFillDir !== 'x' && ownFillDir !== 'y') ownFillDir = '';
// store node for tweaking by selectPoints
cdscatter[0][plotinfo.isRangePlot ? 'nodeRangePlot3' : 'node3'] = tr;
var prevRevpath = '';
var prevPolygons = [];
var prevtrace = trace._prevtrace;
if(prevtrace) {
prevRevpath = prevtrace._prevRevpath || '';
tonext = prevtrace._nextFill;
prevPolygons = prevtrace._polygons;
}
var thispath;
var thisrevpath;
// fullpath is all paths for this curve, joined together straight
// across gaps, for filling
var fullpath = '';
// revpath is fullpath reversed, for fill-to-next
var revpath = '';
// functions for converting a point array to a path
var pathfn, revpathbase, revpathfn;
// variables used before and after the data join
var pt0, lastSegment, pt1, thisPolygons;
// initialize line join data / method
var segments = [];
var makeUpdate = Lib.noop;
ownFillEl3 = trace._ownFill;
if(subTypes.hasLines(trace) || trace.fill !== 'none') {
if(tonext) {
// This tells .style which trace to use for fill information:
tonext.datum(cdscatter);
}
if(['hv', 'vh', 'hvh', 'vhv'].indexOf(line.shape) !== -1) {
pathfn = Drawing.steps(line.shape);
revpathbase = Drawing.steps(
line.shape.split('').reverse().join('')
);
} else if(line.shape === 'spline') {
pathfn = revpathbase = function(pts) {
var pLast = pts[pts.length - 1];
if(pts.length > 1 && pts[0][0] === pLast[0] && pts[0][1] === pLast[1]) {
// identical start and end points: treat it as a
// closed curve so we don't get a kink
return Drawing.smoothclosed(pts.slice(1), line.smoothing);
} else {
return Drawing.smoothopen(pts, line.smoothing);
}
};
} else {
pathfn = revpathbase = function(pts) {
return 'M' + pts.join('L');
};
}
revpathfn = function(pts) {
// note: this is destructive (reverses pts in place) so can't use pts after this
return revpathbase(pts.reverse());
};
segments = linePoints(cdscatter, {
xaxis: xa,
yaxis: ya,
connectGaps: trace.connectgaps,
baseTolerance: Math.max(line.width || 1, 3) / 4,
shape: line.shape,
simplify: line.simplify,
fill: trace.fill
});
// since we already have the pixel segments here, use them to make
// polygons for hover on fill
// TODO: can we skip this if hoveron!=fills? That would mean we
// need to redraw when you change hoveron...
thisPolygons = trace._polygons = new Array(segments.length);
for(i = 0; i < segments.length; i++) {
trace._polygons[i] = polygonTester(segments[i]);
}
if(segments.length) {
pt0 = segments[0][0];
lastSegment = segments[segments.length - 1];
pt1 = lastSegment[lastSegment.length - 1];
}
makeUpdate = function(isEnter) {
return function(pts) {
thispath = pathfn(pts);
thisrevpath = revpathfn(pts);
if(!fullpath) {
fullpath = thispath;
revpath = thisrevpath;
} else if(ownFillDir) {
fullpath += 'L' + thispath.substr(1);
revpath = thisrevpath + ('L' + revpath.substr(1));
} else {
fullpath += 'Z' + thispath;
revpath = thisrevpath + 'Z' + revpath;
}
if(subTypes.hasLines(trace) && pts.length > 1) {
var el = d3.select(this);
// This makes the coloring work correctly:
el.datum(cdscatter);
if(isEnter) {
transition(el.style('opacity', 0)
.attr('d', thispath)
.call(Drawing.lineGroupStyle))
.style('opacity', 1);
} else {
var sel = transition(el);
sel.attr('d', thispath);
Drawing.singleLineStyle(cdscatter, sel);
}
}
};
};
}
var lineJoin = lines.selectAll('.js-line').data(segments);
transition(lineJoin.exit())
.style('opacity', 0)
.remove();
lineJoin.each(makeUpdate(false));
lineJoin.enter().append('path')
.classed('js-line', true)
.style('vector-effect', 'non-scaling-stroke')
.call(Drawing.lineGroupStyle)
.each(makeUpdate(true));
Drawing.setClipUrl(lineJoin, plotinfo.layerClipId, gd);
function clearFill(selection) {
transition(selection).attr('d', 'M0,0Z');
}
if(segments.length) {
if(ownFillEl3) {
ownFillEl3.datum(cdscatter);
if(pt0 && pt1) {
if(ownFillDir) {
if(ownFillDir === 'y') {
pt0[1] = pt1[1] = ya.c2p(0, true);
} else if(ownFillDir === 'x') {
pt0[0] = pt1[0] = xa.c2p(0, true);
}
// fill to zero: full trace path, plus extension of
// the endpoints to the appropriate axis
// For the sake of animations, wrap the points around so that
// the points on the axes are the first two points. Otherwise
// animations get a little crazy if the number of points changes.
transition(ownFillEl3).attr('d', 'M' + pt1 + 'L' + pt0 + 'L' + fullpath.substr(1))
.call(Drawing.singleFillStyle);
} else {
// fill to self: just join the path to itself
transition(ownFillEl3).attr('d', fullpath + 'Z')
.call(Drawing.singleFillStyle);
}
}
} else if(tonext) {
if(trace.fill.substr(0, 6) === 'tonext' && fullpath && prevRevpath) {
// fill to next: full trace path, plus the previous path reversed
if(trace.fill === 'tonext') {
// tonext: for use by concentric shapes, like manually constructed
// contours, we just add the two paths closed on themselves.
// This makes strange results if one path is *not* entirely
// inside the other, but then that is a strange usage.
transition(tonext).attr('d', fullpath + 'Z' + prevRevpath + 'Z')
.call(Drawing.singleFillStyle);
} else {
// tonextx/y: for now just connect endpoints with lines. This is
// the correct behavior if the endpoints are at the same value of
// y/x, but if they *aren't*, we should ideally do more complicated
// things depending on whether the new endpoint projects onto the
// existing curve or off the end of it
transition(tonext).attr('d', fullpath + 'L' + prevRevpath.substr(1) + 'Z')
.call(Drawing.singleFillStyle);
}
trace._polygons = trace._polygons.concat(prevPolygons);
} else {
clearFill(tonext);
trace._polygons = null;
}
}
trace._prevRevpath = revpath;
trace._prevPolygons = thisPolygons;
} else {
if(ownFillEl3) clearFill(ownFillEl3);
else if(tonext) clearFill(tonext);
trace._polygons = trace._prevRevpath = trace._prevPolygons = null;
}
function visFilter(d) {
return d.filter(function(v) { return !v.gap && v.vis; });
}
function visFilterWithGaps(d) {
return d.filter(function(v) { return v.vis; });
}
function gapFilter(d) {
return d.filter(function(v) { return !v.gap; });
}
function keyFunc(d) {
return d.id;
}
// Returns a function if the trace is keyed, otherwise returns undefined
function getKeyFunc(trace) {
if(trace.ids) {
return keyFunc;
}
}
function hideFilter() {
return false;
}
function makePoints(points, text, cdscatter) {
var join, selection, hasNode;
var trace = cdscatter[0].trace;
var showMarkers = subTypes.hasMarkers(trace);
var showText = subTypes.hasText(trace);
var keyFunc = getKeyFunc(trace);
var markerFilter = hideFilter;
var textFilter = hideFilter;
if(showMarkers || showText) {
var showFilter = identity;
// if we're stacking, "infer zero" gap mode gets markers in the
// gap points - because we've inferred a zero there - but other
// modes (currently "interpolate", later "interrupt" hopefully)
// we don't draw generated markers
var stackGroup = trace.stackgroup;
var isInferZero = stackGroup && (
gd._fullLayout._scatterStackOpts[xa._id + ya._id][stackGroup].stackgaps === 'infer zero');
if(trace.marker.maxdisplayed || trace._needsCull) {
showFilter = isInferZero ? visFilterWithGaps : visFilter;
} else if(stackGroup && !isInferZero) {
showFilter = gapFilter;
}
if(showMarkers) markerFilter = showFilter;
if(showText) textFilter = showFilter;
}
// marker points
selection = points.selectAll('path.point');
join = selection.data(markerFilter, keyFunc);
var enter = join.enter().append('path')
.classed('point', true);
if(hasTransition) {
enter
.call(Drawing.pointStyle, trace, gd)
.call(Drawing.translatePoints, xa, ya)
.style('opacity', 0)
.transition()
.style('opacity', 1);
}
join.order();
var styleFns;
if(showMarkers) {
styleFns = Drawing.makePointStyleFns(trace);
}
join.each(function(d) {
var el = d3.select(this);
var sel = transition(el);
hasNode = Drawing.translatePoint(d, sel, xa, ya);
if(hasNode) {
Drawing.singlePointStyle(d, sel, trace, styleFns, gd);
if(plotinfo.layerClipId) {
Drawing.hideOutsideRangePoint(d, sel, xa, ya, trace.xcalendar, trace.ycalendar);
}
if(trace.customdata) {
el.classed('plotly-customdata', d.data !== null && d.data !== undefined);
}
} else {
sel.remove();
}
});
if(hasTransition) {
join.exit().transition()
.style('opacity', 0)
.remove();
} else {
join.exit().remove();
}
// text points
selection = text.selectAll('g');
join = selection.data(textFilter, keyFunc);
// each text needs to go in its own 'g' in case
// it gets converted to mathjax
join.enter().append('g').classed('textpoint', true).append('text');
join.order();
join.each(function(d) {
var g = d3.select(this);
var sel = transition(g.select('text'));
hasNode = Drawing.translatePoint(d, sel, xa, ya);
if(hasNode) {
if(plotinfo.layerClipId) {
Drawing.hideOutsideRangePoint(d, g, xa, ya, trace.xcalendar, trace.ycalendar);
}
} else {
g.remove();
}
});
join.selectAll('text')
.call(Drawing.textPointStyle, trace, gd)
.each(function(d) {
// This just *has* to be totally custom because of SVG text positioning :(
// It's obviously copied from translatePoint; we just can't use that
var x = xa.c2p(d.x);
var y = ya.c2p(d.y);
d3.select(this).selectAll('tspan.line').each(function() {
transition(d3.select(this)).attr({x: x, y: y});
});
});
join.exit().remove();
}
points.datum(cdscatter);
text.datum(cdscatter);
makePoints(points, text, cdscatter);
// lastly, clip points groups of `cliponaxis !== false` traces
// on `plotinfo._hasClipOnAxisFalse === true` subplots
var hasClipOnAxisFalse = trace.cliponaxis === false;
var clipUrl = hasClipOnAxisFalse ? null : plotinfo.layerClipId;
Drawing.setClipUrl(points, clipUrl, gd);
Drawing.setClipUrl(text, clipUrl, gd);
}
function selectMarkers(gd, idx, plotinfo, cdscatter, cdscatterAll) {
var xa = plotinfo.xaxis;
var ya = plotinfo.yaxis;
var xr = d3.extent(Lib.simpleMap(xa.range, xa.r2c));
var yr = d3.extent(Lib.simpleMap(ya.range, ya.r2c));
var trace = cdscatter[0].trace;
if(!subTypes.hasMarkers(trace)) return;
// if marker.maxdisplayed is used, select a maximum of
// mnum markers to show, from the set that are in the viewport
var mnum = trace.marker.maxdisplayed;
// TODO: remove some as we get away from the viewport?
if(mnum === 0) return;
var cd = cdscatter.filter(function(v) {
return v.x >= xr[0] && v.x <= xr[1] && v.y >= yr[0] && v.y <= yr[1];
});
var inc = Math.ceil(cd.length / mnum);
var tnum = 0;
cdscatterAll.forEach(function(cdj, j) {
var tracei = cdj[0].trace;
if(subTypes.hasMarkers(tracei) &&
tracei.marker.maxdisplayed > 0 && j < idx) {
tnum++;
}
});
// if multiple traces use maxdisplayed, stagger which markers we
// display this formula offsets successive traces by 1/3 of the
// increment, adding an extra small amount after each triplet so
// it's not quite periodic
var i0 = Math.round(tnum * inc / 3 + Math.floor(tnum / 3) * inc / 7.1);
// for error bars: save in cd which markers to show
// so we don't have to repeat this
cdscatter.forEach(function(v) { delete v.vis; });
cd.forEach(function(v, i) {
if(Math.round((i + i0) % inc) === 0) v.vis = true;
});
}
},{"../../components/drawing":388,"../../lib":503,"../../lib/polygon":515,"../../registry":638,"./line_points":939,"./link_traces":941,"./subtypes":950,"@plotly/d3":58}],947:[function(_dereq_,module,exports){
'use strict';
var subtypes = _dereq_('./subtypes');
module.exports = function selectPoints(searchInfo, selectionTester) {
var cd = searchInfo.cd;
var xa = searchInfo.xaxis;
var ya = searchInfo.yaxis;
var selection = [];
var trace = cd[0].trace;
var i;
var di;
var x;
var y;
var hasOnlyLines = (!subtypes.hasMarkers(trace) && !subtypes.hasText(trace));
if(hasOnlyLines) return [];
if(selectionTester === false) { // clear selection
for(i = 0; i < cd.length; i++) {
cd[i].selected = 0;
}
} else {
for(i = 0; i < cd.length; i++) {
di = cd[i];
x = xa.c2p(di.x);
y = ya.c2p(di.y);
if((di.i !== null) && selectionTester.contains([x, y], false, i, searchInfo)) {
selection.push({
pointNumber: di.i,
x: xa.c2d(di.x),
y: ya.c2d(di.y)
});
di.selected = 1;
} else {
di.selected = 0;
}
}
}
return selection;
};
},{"./subtypes":950}],948:[function(_dereq_,module,exports){
'use strict';
var perStackAttrs = ['orientation', 'groupnorm', 'stackgaps'];
module.exports = function handleStackDefaults(traceIn, traceOut, layout, coerce) {
var stackOpts = layout._scatterStackOpts;
var stackGroup = coerce('stackgroup');
if(stackGroup) {
// use independent stacking options per subplot
var subplot = traceOut.xaxis + traceOut.yaxis;
var subplotStackOpts = stackOpts[subplot];
if(!subplotStackOpts) subplotStackOpts = stackOpts[subplot] = {};
var groupOpts = subplotStackOpts[stackGroup];
var firstTrace = false;
if(groupOpts) {
groupOpts.traces.push(traceOut);
} else {
groupOpts = subplotStackOpts[stackGroup] = {
// keep track of trace indices for use during stacking calculations
// this will be filled in during `calc` and used during `crossTraceCalc`
// so it's OK if we don't recreate it during a non-calc edit
traceIndices: [],
// Hold on to the whole set of prior traces
// First one is most important, so we can clear defaults
// there if we find explicit values only in later traces.
// We're only going to *use* the values stored in groupOpts,
// but for the editor and validate we want things self-consistent
// The full set of traces is used only to fix `fill` default if
// we find `orientation: 'h'` beyond the first trace
traces: [traceOut]
};
firstTrace = true;
}
// TODO: how is this going to work with groupby transforms?
// in principle it should be OK I guess, as long as explicit group styles
// don't override explicit base-trace styles?
var dflts = {
orientation: (traceOut.x && !traceOut.y) ? 'h' : 'v'
};
for(var i = 0; i < perStackAttrs.length; i++) {
var attr = perStackAttrs[i];
var attrFound = attr + 'Found';
if(!groupOpts[attrFound]) {
var traceHasAttr = traceIn[attr] !== undefined;
var isOrientation = attr === 'orientation';
if(traceHasAttr || firstTrace) {
groupOpts[attr] = coerce(attr, dflts[attr]);
if(isOrientation) {
groupOpts.fillDflt = groupOpts[attr] === 'h' ?
'tonextx' : 'tonexty';
}
if(traceHasAttr) {
// Note: this will show a value here even if it's invalid
// in which case it will revert to default.
groupOpts[attrFound] = true;
// Note: only one trace in the stack will get a _fullData
// entry for a given stack-wide attribute. If no traces
// (or the first trace) specify that attribute, the
// first trace will get it. If the first trace does NOT
// specify it but some later trace does, then it gets
// removed from the first trace and only included in the
// one that specified it. This is mostly important for
// editors (that want to see the full values to know
// what settings are available) and Plotly.react diffing.
// Editors may want to use fullLayout._scatterStackOpts
// directly and make these settings available from all
// traces in the stack... then set the new value into
// the first trace, and clear all later traces.
if(!firstTrace) {
delete groupOpts.traces[0][attr];
// orientation can affect default fill of previous traces
if(isOrientation) {
for(var j = 0; j < groupOpts.traces.length - 1; j++) {
var trace2 = groupOpts.traces[j];
if(trace2._input.fill !== trace2.fill) {
trace2.fill = groupOpts.fillDflt;
}
}
}
}
}
}
}
}
return groupOpts;
}
};
},{}],949:[function(_dereq_,module,exports){
'use strict';
var d3 = _dereq_('@plotly/d3');
var Drawing = _dereq_('../../components/drawing');
var Registry = _dereq_('../../registry');
function style(gd) {
var s = d3.select(gd).selectAll('g.trace.scatter');
s.style('opacity', function(d) {
return d[0].trace.opacity;
});
s.selectAll('g.points').each(function(d) {
var sel = d3.select(this);
var trace = d.trace || d[0].trace;
stylePoints(sel, trace, gd);
});
s.selectAll('g.text').each(function(d) {
var sel = d3.select(this);
var trace = d.trace || d[0].trace;
styleText(sel, trace, gd);
});
s.selectAll('g.trace path.js-line')
.call(Drawing.lineGroupStyle);
s.selectAll('g.trace path.js-fill')
.call(Drawing.fillGroupStyle);
Registry.getComponentMethod('errorbars', 'style')(s);
}
function stylePoints(sel, trace, gd) {
Drawing.pointStyle(sel.selectAll('path.point'), trace, gd);
}
function styleText(sel, trace, gd) {
Drawing.textPointStyle(sel.selectAll('text'), trace, gd);
}
function styleOnSelect(gd, cd, sel) {
var trace = cd[0].trace;
if(trace.selectedpoints) {
Drawing.selectedPointStyle(sel.selectAll('path.point'), trace);
Drawing.selectedTextStyle(sel.selectAll('text'), trace);
} else {
stylePoints(sel, trace, gd);
styleText(sel, trace, gd);
}
}
module.exports = {
style: style,
stylePoints: stylePoints,
styleText: styleText,
styleOnSelect: styleOnSelect
};
},{"../../components/drawing":388,"../../registry":638,"@plotly/d3":58}],950:[function(_dereq_,module,exports){
'use strict';
var Lib = _dereq_('../../lib');
module.exports = {
hasLines: function(trace) {
return trace.visible && trace.mode &&
trace.mode.indexOf('lines') !== -1;
},
hasMarkers: function(trace) {
return trace.visible && (
(trace.mode && trace.mode.indexOf('markers') !== -1) ||
// until splom implements 'mode'
trace.type === 'splom'
);
},
hasText: function(trace) {
return trace.visible && trace.mode &&
trace.mode.indexOf('text') !== -1;
},
isBubble: function(trace) {
return Lib.isPlainObject(trace.marker) &&
Lib.isArrayOrTypedArray(trace.marker.size);
}
};
},{"../../lib":503}],951:[function(_dereq_,module,exports){
'use strict';
var Lib = _dereq_('../../lib');
/*
* opts: object of flags to control features not all text users support
* noSelect: caller does not support selected/unselected attribute containers
*/
module.exports = function(traceIn, traceOut, layout, coerce, opts) {
opts = opts || {};
coerce('textposition');
Lib.coerceFont(coerce, 'textfont', layout.font);
if(!opts.noSelect) {
coerce('selected.textfont.color');
coerce('unselected.textfont.color');
}
};
},{"../../lib":503}],952:[function(_dereq_,module,exports){
'use strict';
var Lib = _dereq_('../../lib');
var Registry = _dereq_('../../registry');
module.exports = function handleXYDefaults(traceIn, traceOut, layout, coerce) {
var x = coerce('x');
var y = coerce('y');
var len;
var handleCalendarDefaults = Registry.getComponentMethod('calendars', 'handleTraceDefaults');
handleCalendarDefaults(traceIn, traceOut, ['x', 'y'], layout);
if(x) {
var xlen = Lib.minRowLength(x);
if(y) {
len = Math.min(xlen, Lib.minRowLength(y));
} else {
len = xlen;
coerce('y0');
coerce('dy');
}
} else {
if(!y) return 0;
len = Lib.minRowLength(y);
coerce('x0');
coerce('dx');
}
traceOut._length = len;
return len;
};
},{"../../lib":503,"../../registry":638}],953:[function(_dereq_,module,exports){
'use strict';
var scatterAttrs = _dereq_('../scatter/attributes');
var colorAttributes = _dereq_('../../components/colorscale/attributes');
var axisHoverFormat = _dereq_('../../plots/cartesian/axis_format_attributes').axisHoverFormat;
var hovertemplateAttrs = _dereq_('../../plots/template_attributes').hovertemplateAttrs;
var texttemplateAttrs = _dereq_('../../plots/template_attributes').texttemplateAttrs;
var baseAttrs = _dereq_('../../plots/attributes');
var DASHES = _dereq_('../../constants/gl3d_dashes');
var MARKER_SYMBOLS = _dereq_('../../constants/gl3d_markers');
var extendFlat = _dereq_('../../lib/extend').extendFlat;
var overrideAll = _dereq_('../../plot_api/edit_types').overrideAll;
var sortObjectKeys = _dereq_('../../lib/sort_object_keys');
var scatterLineAttrs = scatterAttrs.line;
var scatterMarkerAttrs = scatterAttrs.marker;
var scatterMarkerLineAttrs = scatterMarkerAttrs.line;
var lineAttrs = extendFlat({
width: scatterLineAttrs.width,
dash: {
valType: 'enumerated',
values: sortObjectKeys(DASHES),
dflt: 'solid',
}
}, colorAttributes('line'));
function makeProjectionAttr(axLetter) {
return {
show: {
valType: 'boolean',
dflt: false,
},
opacity: {
valType: 'number',
min: 0,
max: 1,
dflt: 1,
},
scale: {
valType: 'number',
min: 0,
max: 10,
dflt: 2 / 3,
}
};
}
var attrs = module.exports = overrideAll({
x: scatterAttrs.x,
y: scatterAttrs.y,
z: {
valType: 'data_array',
},
text: extendFlat({}, scatterAttrs.text, {
}),
texttemplate: texttemplateAttrs({}, {
}),
hovertext: extendFlat({}, scatterAttrs.hovertext, {
}),
hovertemplate: hovertemplateAttrs(),
xhoverformat: axisHoverFormat('x'),
yhoverformat: axisHoverFormat('y'),
zhoverformat: axisHoverFormat('z'),
mode: extendFlat({}, scatterAttrs.mode, // shouldn't this be on-par with 2D?
{dflt: 'lines+markers'}),
surfaceaxis: {
valType: 'enumerated',
values: [-1, 0, 1, 2],
dflt: -1,
},
surfacecolor: {
valType: 'color',
},
projection: {
x: makeProjectionAttr('x'),
y: makeProjectionAttr('y'),
z: makeProjectionAttr('z')
},
connectgaps: scatterAttrs.connectgaps,
line: lineAttrs,
marker: extendFlat({ // Parity with scatter.js?
symbol: {
valType: 'enumerated',
values: sortObjectKeys(MARKER_SYMBOLS),
dflt: 'circle',
arrayOk: true,
},
size: extendFlat({}, scatterMarkerAttrs.size, {dflt: 8}),
sizeref: scatterMarkerAttrs.sizeref,
sizemin: scatterMarkerAttrs.sizemin,
sizemode: scatterMarkerAttrs.sizemode,
opacity: extendFlat({}, scatterMarkerAttrs.opacity, {
arrayOk: false,
}),
colorbar: scatterMarkerAttrs.colorbar,
line: extendFlat({
width: extendFlat({}, scatterMarkerLineAttrs.width, {arrayOk: false})
},
colorAttributes('marker.line')
)
},
colorAttributes('marker')
),
textposition: extendFlat({}, scatterAttrs.textposition, {dflt: 'top center'}),
textfont: {
color: scatterAttrs.textfont.color,
size: scatterAttrs.textfont.size,
family: extendFlat({}, scatterAttrs.textfont.family, {arrayOk: false})
},
opacity: baseAttrs.opacity,
hoverinfo: extendFlat({}, baseAttrs.hoverinfo)
}, 'calc', 'nested');
attrs.x.editType = attrs.y.editType = attrs.z.editType = 'calc+clearAxisTypes';
},{"../../components/colorscale/attributes":373,"../../constants/gl3d_dashes":476,"../../constants/gl3d_markers":477,"../../lib/extend":493,"../../lib/sort_object_keys":526,"../../plot_api/edit_types":536,"../../plots/attributes":550,"../../plots/cartesian/axis_format_attributes":557,"../../plots/template_attributes":633,"../scatter/attributes":925}],954:[function(_dereq_,module,exports){
'use strict';
var arraysToCalcdata = _dereq_('../scatter/arrays_to_calcdata');
var calcColorscale = _dereq_('../scatter/colorscale_calc');
/**
* This is a kludge to put the array attributes into
* calcdata the way Scatter.plot does, so that legends and
* popovers know what to do with them.
*/
module.exports = function calc(gd, trace) {
var cd = [{x: false, y: false, trace: trace, t: {}}];
arraysToCalcdata(cd, trace);
calcColorscale(gd, trace);
return cd;
};
},{"../scatter/arrays_to_calcdata":924,"../scatter/colorscale_calc":928}],955:[function(_dereq_,module,exports){
'use strict';
var Registry = _dereq_('../../registry');
function calculateAxisErrors(data, params, scaleFactor, axis) {
if(!params || !params.visible) return null;
var computeError = Registry.getComponentMethod('errorbars', 'makeComputeError')(params);
var result = new Array(data.length);
for(var i = 0; i < data.length; i++) {
var errors = computeError(+data[i], i);
if(axis.type === 'log') {
var point = axis.c2l(data[i]);
var min = data[i] - errors[0];
var max = data[i] + errors[1];
result[i] = [
(axis.c2l(min, true) - point) * scaleFactor,
(axis.c2l(max, true) - point) * scaleFactor
];
// Keep track of the lower error bound which isn't negative!
if(min > 0) {
var lower = axis.c2l(min);
if(!axis._lowerLogErrorBound) axis._lowerLogErrorBound = lower;
axis._lowerErrorBound = Math.min(axis._lowerLogErrorBound, lower);
}
} else {
result[i] = [
-errors[0] * scaleFactor,
errors[1] * scaleFactor
];
}
}
return result;
}
function dataLength(array) {
for(var i = 0; i < array.length; i++) {
if(array[i]) return array[i].length;
}
return 0;
}
function calculateErrors(data, scaleFactor, sceneLayout) {
var errors = [
calculateAxisErrors(data.x, data.error_x, scaleFactor[0], sceneLayout.xaxis),
calculateAxisErrors(data.y, data.error_y, scaleFactor[1], sceneLayout.yaxis),
calculateAxisErrors(data.z, data.error_z, scaleFactor[2], sceneLayout.zaxis)
];
var n = dataLength(errors);
if(n === 0) return null;
var errorBounds = new Array(n);
for(var i = 0; i < n; i++) {
var bound = [[0, 0, 0], [0, 0, 0]];
for(var j = 0; j < 3; j++) {
if(errors[j]) {
for(var k = 0; k < 2; k++) {
bound[k][j] = errors[j][i][k];
}
}
}
errorBounds[i] = bound;
}
return errorBounds;
}
module.exports = calculateErrors;
},{"../../registry":638}],956:[function(_dereq_,module,exports){
'use strict';
var createLinePlot = _dereq_('../../../stackgl_modules').gl_line3d;
var createScatterPlot = _dereq_('../../../stackgl_modules').gl_scatter3d;
var createErrorBars = _dereq_('../../../stackgl_modules').gl_error3d;
var createMesh = _dereq_('../../../stackgl_modules').gl_mesh3d;
var triangulate = _dereq_('../../../stackgl_modules').delaunay_triangulate;
var Lib = _dereq_('../../lib');
var str2RgbaArray = _dereq_('../../lib/str2rgbarray');
var formatColor = _dereq_('../../lib/gl_format_color').formatColor;
var makeBubbleSizeFn = _dereq_('../scatter/make_bubble_size_func');
var DASH_PATTERNS = _dereq_('../../constants/gl3d_dashes');
var MARKER_SYMBOLS = _dereq_('../../constants/gl3d_markers');
var Axes = _dereq_('../../plots/cartesian/axes');
var appendArrayPointValue = _dereq_('../../components/fx/helpers').appendArrayPointValue;
var calculateError = _dereq_('./calc_errors');
function LineWithMarkers(scene, uid) {
this.scene = scene;
this.uid = uid;
this.linePlot = null;
this.scatterPlot = null;
this.errorBars = null;
this.textMarkers = null;
this.delaunayMesh = null;
this.color = null;
this.mode = '';
this.dataPoints = [];
this.axesBounds = [
[-Infinity, -Infinity, -Infinity],
[Infinity, Infinity, Infinity]
];
this.textLabels = null;
this.data = null;
}
var proto = LineWithMarkers.prototype;
proto.handlePick = function(selection) {
if(selection.object &&
(selection.object === this.linePlot ||
selection.object === this.delaunayMesh ||
selection.object === this.textMarkers ||
selection.object === this.scatterPlot)
) {
var ind = selection.index = selection.data.index;
if(selection.object.highlight) {
selection.object.highlight(null);
}
if(this.scatterPlot) {
selection.object = this.scatterPlot;
this.scatterPlot.highlight(selection.data);
}
selection.textLabel = '';
if(this.textLabels) {
if(Array.isArray(this.textLabels)) {
if(this.textLabels[ind] || this.textLabels[ind] === 0) {
selection.textLabel = this.textLabels[ind];
}
} else {
selection.textLabel = this.textLabels;
}
}
selection.traceCoordinate = [
this.data.x[ind],
this.data.y[ind],
this.data.z[ind]
];
return true;
}
};
function constructDelaunay(points, color, axis) {
var u = (axis + 1) % 3;
var v = (axis + 2) % 3;
var filteredPoints = [];
var filteredIds = [];
var i;
for(i = 0; i < points.length; ++i) {
var p = points[i];
if(isNaN(p[u]) || !isFinite(p[u]) ||
isNaN(p[v]) || !isFinite(p[v])) {
continue;
}
filteredPoints.push([p[u], p[v]]);
filteredIds.push(i);
}
var cells = triangulate(filteredPoints);
for(i = 0; i < cells.length; ++i) {
var c = cells[i];
for(var j = 0; j < c.length; ++j) {
c[j] = filteredIds[c[j]];
}
}
return {
positions: points,
cells: cells,
meshColor: color
};
}
function calculateErrorParams(errors) {
var capSize = [0.0, 0.0, 0.0];
var color = [[0, 0, 0], [0, 0, 0], [0, 0, 0]];
var lineWidth = [1.0, 1.0, 1.0];
for(var i = 0; i < 3; i++) {
var e = errors[i];
if(e && e.copy_zstyle !== false && errors[2].visible !== false) e = errors[2];
if(!e || !e.visible) continue;
capSize[i] = e.width / 2; // ballpark rescaling
color[i] = str2RgbaArray(e.color);
lineWidth[i] = e.thickness;
}
return {capSize: capSize, color: color, lineWidth: lineWidth};
}
function parseAlignmentX(a) {
if(a === null || a === undefined) return 0;
return (a.indexOf('left') > -1) ? -1 :
(a.indexOf('right') > -1) ? 1 : 0;
}
function parseAlignmentY(a) {
if(a === null || a === undefined) return 0;
return (a.indexOf('top') > -1) ? -1 :
(a.indexOf('bottom') > -1) ? 1 : 0;
}
function calculateTextOffset(tp) {
// Read out text properties
var defaultAlignmentX = 0;
var defaultAlignmentY = 0;
var textOffset = [
defaultAlignmentX,
defaultAlignmentY
];
if(Array.isArray(tp)) {
for(var i = 0; i < tp.length; i++) {
textOffset[i] = [
defaultAlignmentX,
defaultAlignmentY
];
if(tp[i]) {
textOffset[i][0] = parseAlignmentX(tp[i]);
textOffset[i][1] = parseAlignmentY(tp[i]);
}
}
} else {
textOffset[0] = parseAlignmentX(tp);
textOffset[1] = parseAlignmentY(tp);
}
return textOffset;
}
function calculateSize(sizeIn, sizeFn) {
// rough parity with Plotly 2D markers
return sizeFn(sizeIn * 4);
}
function calculateSymbol(symbolIn) {
return MARKER_SYMBOLS[symbolIn];
}
function formatParam(paramIn, len, calculate, dflt, extraFn) {
var paramOut = null;
if(Lib.isArrayOrTypedArray(paramIn)) {
paramOut = [];
for(var i = 0; i < len; i++) {
if(paramIn[i] === undefined) paramOut[i] = dflt;
else paramOut[i] = calculate(paramIn[i], extraFn);
}
} else paramOut = calculate(paramIn, Lib.identity);
return paramOut;
}
function convertPlotlyOptions(scene, data) {
var points = [];
var sceneLayout = scene.fullSceneLayout;
var scaleFactor = scene.dataScale;
var xaxis = sceneLayout.xaxis;
var yaxis = sceneLayout.yaxis;
var zaxis = sceneLayout.zaxis;
var marker = data.marker;
var line = data.line;
var x = data.x || [];
var y = data.y || [];
var z = data.z || [];
var len = x.length;
var xcalendar = data.xcalendar;
var ycalendar = data.ycalendar;
var zcalendar = data.zcalendar;
var xc, yc, zc;
var params, i;
var text;
// Convert points
for(i = 0; i < len; i++) {
// sanitize numbers and apply transforms based on axes.type
xc = xaxis.d2l(x[i], 0, xcalendar) * scaleFactor[0];
yc = yaxis.d2l(y[i], 0, ycalendar) * scaleFactor[1];
zc = zaxis.d2l(z[i], 0, zcalendar) * scaleFactor[2];
points[i] = [xc, yc, zc];
}
// convert text
if(Array.isArray(data.text)) text = data.text;
else if(data.text !== undefined) {
text = new Array(len);
for(i = 0; i < len; i++) text[i] = data.text;
}
function formatter(axName, val) {
var ax = sceneLayout[axName];
return Axes.tickText(ax, ax.d2l(val), true).text;
}
// check texttemplate
var texttemplate = data.texttemplate;
if(texttemplate) {
var fullLayout = scene.fullLayout;
var d3locale = fullLayout._d3locale;
var isArray = Array.isArray(texttemplate);
var N = isArray ? Math.min(texttemplate.length, len) : len;
var txt = isArray ?
function(i) { return texttemplate[i]; } :
function() { return texttemplate; };
text = new Array(N);
for(i = 0; i < N; i++) {
var d = {x: x[i], y: y[i], z: z[i]};
var labels = {
xLabel: formatter('xaxis', x[i]),
yLabel: formatter('yaxis', y[i]),
zLabel: formatter('zaxis', z[i])
};
var pointValues = {};
appendArrayPointValue(pointValues, data, i);
var meta = data._meta || {};
text[i] = Lib.texttemplateString(txt(i), labels, d3locale, pointValues, d, meta);
}
}
// Build object parameters
params = {
position: points,
mode: data.mode,
text: text
};
if('line' in data) {
params.lineColor = formatColor(line, 1, len);
params.lineWidth = line.width;
params.lineDashes = line.dash;
}
if('marker' in data) {
var sizeFn = makeBubbleSizeFn(data);
params.scatterColor = formatColor(marker, 1, len);
params.scatterSize = formatParam(marker.size, len, calculateSize, 20, sizeFn);
params.scatterMarker = formatParam(marker.symbol, len, calculateSymbol, '●');
params.scatterLineWidth = marker.line.width; // arrayOk === false
params.scatterLineColor = formatColor(marker.line, 1, len);
params.scatterAngle = 0;
}
if('textposition' in data) {
params.textOffset = calculateTextOffset(data.textposition);
params.textColor = formatColor(data.textfont, 1, len);
params.textSize = formatParam(data.textfont.size, len, Lib.identity, 12);
params.textFont = data.textfont.family; // arrayOk === false
params.textAngle = 0;
}
var dims = ['x', 'y', 'z'];
params.project = [false, false, false];
params.projectScale = [1, 1, 1];
params.projectOpacity = [1, 1, 1];
for(i = 0; i < 3; ++i) {
var projection = data.projection[dims[i]];
if((params.project[i] = projection.show)) {
params.projectOpacity[i] = projection.opacity;
params.projectScale[i] = projection.scale;
}
}
params.errorBounds = calculateError(data, scaleFactor, sceneLayout);
var errorParams = calculateErrorParams([data.error_x, data.error_y, data.error_z]);
params.errorColor = errorParams.color;
params.errorLineWidth = errorParams.lineWidth;
params.errorCapSize = errorParams.capSize;
params.delaunayAxis = data.surfaceaxis;
params.delaunayColor = str2RgbaArray(data.surfacecolor);
return params;
}
function _arrayToColor(color) {
if(Lib.isArrayOrTypedArray(color)) {
var c = color[0];
if(Lib.isArrayOrTypedArray(c)) color = c;
return 'rgb(' + color.slice(0, 3).map(function(x) {
return Math.round(x * 255);
}) + ')';
}
return null;
}
function arrayToColor(colors) {
if(!Lib.isArrayOrTypedArray(colors)) {
return null;
}
if((colors.length === 4) && (typeof colors[0] === 'number')) {
return _arrayToColor(colors);
}
return colors.map(_arrayToColor);
}
proto.update = function(data) {
var gl = this.scene.glplot.gl;
var lineOptions;
var scatterOptions;
var errorOptions;
var textOptions;
var dashPattern = DASH_PATTERNS.solid;
// Save data
this.data = data;
// Run data conversion
var options = convertPlotlyOptions(this.scene, data);
if('mode' in options) {
this.mode = options.mode;
}
if('lineDashes' in options) {
if(options.lineDashes in DASH_PATTERNS) {
dashPattern = DASH_PATTERNS[options.lineDashes];
}
}
this.color = arrayToColor(options.scatterColor) ||
arrayToColor(options.lineColor);
// Save data points
this.dataPoints = options.position;
lineOptions = {
gl: this.scene.glplot.gl,
position: options.position,
color: options.lineColor,
lineWidth: options.lineWidth || 1,
dashes: dashPattern[0],
dashScale: dashPattern[1],
opacity: data.opacity,
connectGaps: data.connectgaps
};
if(this.mode.indexOf('lines') !== -1) {
if(this.linePlot) this.linePlot.update(lineOptions);
else {
this.linePlot = createLinePlot(lineOptions);
this.linePlot._trace = this;
this.scene.glplot.add(this.linePlot);
}
} else if(this.linePlot) {
this.scene.glplot.remove(this.linePlot);
this.linePlot.dispose();
this.linePlot = null;
}
// N.B. marker.opacity must be a scalar for performance
var scatterOpacity = data.opacity;
if(data.marker && data.marker.opacity) scatterOpacity *= data.marker.opacity;
scatterOptions = {
gl: this.scene.glplot.gl,
position: options.position,
color: options.scatterColor,
size: options.scatterSize,
glyph: options.scatterMarker,
opacity: scatterOpacity,
orthographic: true,
lineWidth: options.scatterLineWidth,
lineColor: options.scatterLineColor,
project: options.project,
projectScale: options.projectScale,
projectOpacity: options.projectOpacity
};
if(this.mode.indexOf('markers') !== -1) {
if(this.scatterPlot) this.scatterPlot.update(scatterOptions);
else {
this.scatterPlot = createScatterPlot(scatterOptions);
this.scatterPlot._trace = this;
this.scatterPlot.highlightScale = 1;
this.scene.glplot.add(this.scatterPlot);
}
} else if(this.scatterPlot) {
this.scene.glplot.remove(this.scatterPlot);
this.scatterPlot.dispose();
this.scatterPlot = null;
}
textOptions = {
gl: this.scene.glplot.gl,
position: options.position,
glyph: options.text,
color: options.textColor,
size: options.textSize,
angle: options.textAngle,
alignment: options.textOffset,
font: options.textFont,
orthographic: true,
lineWidth: 0,
project: false,
opacity: data.opacity
};
this.textLabels = data.hovertext || data.text;
if(this.mode.indexOf('text') !== -1) {
if(this.textMarkers) this.textMarkers.update(textOptions);
else {
this.textMarkers = createScatterPlot(textOptions);
this.textMarkers._trace = this;
this.textMarkers.highlightScale = 1;
this.scene.glplot.add(this.textMarkers);
}
} else if(this.textMarkers) {
this.scene.glplot.remove(this.textMarkers);
this.textMarkers.dispose();
this.textMarkers = null;
}
errorOptions = {
gl: this.scene.glplot.gl,
position: options.position,
color: options.errorColor,
error: options.errorBounds,
lineWidth: options.errorLineWidth,
capSize: options.errorCapSize,
opacity: data.opacity
};
if(this.errorBars) {
if(options.errorBounds) {
this.errorBars.update(errorOptions);
} else {
this.scene.glplot.remove(this.errorBars);
this.errorBars.dispose();
this.errorBars = null;
}
} else if(options.errorBounds) {
this.errorBars = createErrorBars(errorOptions);
this.errorBars._trace = this;
this.scene.glplot.add(this.errorBars);
}
if(options.delaunayAxis >= 0) {
var delaunayOptions = constructDelaunay(
options.position,
options.delaunayColor,
options.delaunayAxis
);
delaunayOptions.opacity = data.opacity;
if(this.delaunayMesh) {
this.delaunayMesh.update(delaunayOptions);
} else {
delaunayOptions.gl = gl;
this.delaunayMesh = createMesh(delaunayOptions);
this.delaunayMesh._trace = this;
this.scene.glplot.add(this.delaunayMesh);
}
} else if(this.delaunayMesh) {
this.scene.glplot.remove(this.delaunayMesh);
this.delaunayMesh.dispose();
this.delaunayMesh = null;
}
};
proto.dispose = function() {
if(this.linePlot) {
this.scene.glplot.remove(this.linePlot);
this.linePlot.dispose();
}
if(this.scatterPlot) {
this.scene.glplot.remove(this.scatterPlot);
this.scatterPlot.dispose();
}
if(this.errorBars) {
this.scene.glplot.remove(this.errorBars);
this.errorBars.dispose();
}
if(this.textMarkers) {
this.scene.glplot.remove(this.textMarkers);
this.textMarkers.dispose();
}
if(this.delaunayMesh) {
this.scene.glplot.remove(this.delaunayMesh);
this.delaunayMesh.dispose();
}
};
function createLineWithMarkers(scene, data) {
var plot = new LineWithMarkers(scene, data.uid);
plot.update(data);
return plot;
}
module.exports = createLineWithMarkers;
},{"../../../stackgl_modules":1119,"../../components/fx/helpers":402,"../../constants/gl3d_dashes":476,"../../constants/gl3d_markers":477,"../../lib":503,"../../lib/gl_format_color":499,"../../lib/str2rgbarray":528,"../../plots/cartesian/axes":554,"../scatter/make_bubble_size_func":942,"./calc_errors":955}],957:[function(_dereq_,module,exports){
'use strict';
var Registry = _dereq_('../../registry');
var Lib = _dereq_('../../lib');
var subTypes = _dereq_('../scatter/subtypes');
var handleMarkerDefaults = _dereq_('../scatter/marker_defaults');
var handleLineDefaults = _dereq_('../scatter/line_defaults');
var handleTextDefaults = _dereq_('../scatter/text_defaults');
var attributes = _dereq_('./attributes');
module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout) {
function coerce(attr, dflt) {
return Lib.coerce(traceIn, traceOut, attributes, attr, dflt);
}
var len = handleXYZDefaults(traceIn, traceOut, coerce, layout);
if(!len) {
traceOut.visible = false;
return;
}
coerce('text');
coerce('hovertext');
coerce('hovertemplate');
coerce('xhoverformat');
coerce('yhoverformat');
coerce('zhoverformat');
coerce('mode');
if(subTypes.hasLines(traceOut)) {
coerce('connectgaps');
handleLineDefaults(traceIn, traceOut, defaultColor, layout, coerce);
}
if(subTypes.hasMarkers(traceOut)) {
handleMarkerDefaults(traceIn, traceOut, defaultColor, layout, coerce, {noSelect: true});
}
if(subTypes.hasText(traceOut)) {
coerce('texttemplate');
handleTextDefaults(traceIn, traceOut, layout, coerce, {noSelect: true});
}
var lineColor = (traceOut.line || {}).color;
var markerColor = (traceOut.marker || {}).color;
if(coerce('surfaceaxis') >= 0) coerce('surfacecolor', lineColor || markerColor);
var dims = ['x', 'y', 'z'];
for(var i = 0; i < 3; ++i) {
var projection = 'projection.' + dims[i];
if(coerce(projection + '.show')) {
coerce(projection + '.opacity');
coerce(projection + '.scale');
}
}
var errorBarsSupplyDefaults = Registry.getComponentMethod('errorbars', 'supplyDefaults');
errorBarsSupplyDefaults(traceIn, traceOut, lineColor || markerColor || defaultColor, {axis: 'z'});
errorBarsSupplyDefaults(traceIn, traceOut, lineColor || markerColor || defaultColor, {axis: 'y', inherit: 'z'});
errorBarsSupplyDefaults(traceIn, traceOut, lineColor || markerColor || defaultColor, {axis: 'x', inherit: 'z'});
};
function handleXYZDefaults(traceIn, traceOut, coerce, layout) {
var len = 0;
var x = coerce('x');
var y = coerce('y');
var z = coerce('z');
var handleCalendarDefaults = Registry.getComponentMethod('calendars', 'handleTraceDefaults');
handleCalendarDefaults(traceIn, traceOut, ['x', 'y', 'z'], layout);
if(x && y && z) {
// TODO: what happens if one is missing?
len = Math.min(x.length, y.length, z.length);
traceOut._length = traceOut._xlength = traceOut._ylength = traceOut._zlength = len;
}
return len;
}
},{"../../lib":503,"../../registry":638,"../scatter/line_defaults":938,"../scatter/marker_defaults":944,"../scatter/subtypes":950,"../scatter/text_defaults":951,"./attributes":953}],958:[function(_dereq_,module,exports){
'use strict';
module.exports = {
plot: _dereq_('./convert'),
attributes: _dereq_('./attributes'),
markerSymbols: _dereq_('../../constants/gl3d_markers'),
supplyDefaults: _dereq_('./defaults'),
colorbar: [
{
container: 'marker',
min: 'cmin',
max: 'cmax'
}, {
container: 'line',
min: 'cmin',
max: 'cmax'
}
],
calc: _dereq_('./calc'),
moduleType: 'trace',
name: 'scatter3d',
basePlotModule: _dereq_('../../plots/gl3d'),
categories: ['gl3d', 'symbols', 'showLegend', 'scatter-like'],
meta: {
}
};
},{"../../constants/gl3d_markers":477,"../../plots/gl3d":598,"./attributes":953,"./calc":954,"./convert":956,"./defaults":957}],959:[function(_dereq_,module,exports){
'use strict';
var scatterAttrs = _dereq_('../scatter/attributes');
var baseAttrs = _dereq_('../../plots/attributes');
var hovertemplateAttrs = _dereq_('../../plots/template_attributes').hovertemplateAttrs;
var texttemplateAttrs = _dereq_('../../plots/template_attributes').texttemplateAttrs;
var colorScaleAttrs = _dereq_('../../components/colorscale/attributes');
var extendFlat = _dereq_('../../lib/extend').extendFlat;
var scatterMarkerAttrs = scatterAttrs.marker;
var scatterLineAttrs = scatterAttrs.line;
var scatterMarkerLineAttrs = scatterMarkerAttrs.line;
module.exports = {
carpet: {
valType: 'string',
editType: 'calc',
},
a: {
valType: 'data_array',
editType: 'calc',
},
b: {
valType: 'data_array',
editType: 'calc',
},
mode: extendFlat({}, scatterAttrs.mode, {dflt: 'markers'}),
text: extendFlat({}, scatterAttrs.text, {
}),
texttemplate: texttemplateAttrs({editType: 'plot'}, {
keys: ['a', 'b', 'text']
}),
hovertext: extendFlat({}, scatterAttrs.hovertext, {
}),
line: {
color: scatterLineAttrs.color,
width: scatterLineAttrs.width,
dash: scatterLineAttrs.dash,
shape: extendFlat({}, scatterLineAttrs.shape,
{values: ['linear', 'spline']}),
smoothing: scatterLineAttrs.smoothing,
editType: 'calc'
},
connectgaps: scatterAttrs.connectgaps,
fill: extendFlat({}, scatterAttrs.fill, {
values: ['none', 'toself', 'tonext'],
dflt: 'none',
}),
fillcolor: scatterAttrs.fillcolor,
marker: extendFlat({
symbol: scatterMarkerAttrs.symbol,
opacity: scatterMarkerAttrs.opacity,
maxdisplayed: scatterMarkerAttrs.maxdisplayed,
size: scatterMarkerAttrs.size,
sizeref: scatterMarkerAttrs.sizeref,
sizemin: scatterMarkerAttrs.sizemin,
sizemode: scatterMarkerAttrs.sizemode,
line: extendFlat({
width: scatterMarkerLineAttrs.width,
editType: 'calc'
},
colorScaleAttrs('marker.line')
),
gradient: scatterMarkerAttrs.gradient,
editType: 'calc'
},
colorScaleAttrs('marker')
),
textfont: scatterAttrs.textfont,
textposition: scatterAttrs.textposition,
selected: scatterAttrs.selected,
unselected: scatterAttrs.unselected,
hoverinfo: extendFlat({}, baseAttrs.hoverinfo, {
flags: ['a', 'b', 'text', 'name']
}),
hoveron: scatterAttrs.hoveron,
hovertemplate: hovertemplateAttrs()
};
},{"../../components/colorscale/attributes":373,"../../lib/extend":493,"../../plots/attributes":550,"../../plots/template_attributes":633,"../scatter/attributes":925}],960:[function(_dereq_,module,exports){
'use strict';
var isNumeric = _dereq_('fast-isnumeric');
var calcColorscale = _dereq_('../scatter/colorscale_calc');
var arraysToCalcdata = _dereq_('../scatter/arrays_to_calcdata');
var calcSelection = _dereq_('../scatter/calc_selection');
var calcMarkerSize = _dereq_('../scatter/calc').calcMarkerSize;
var lookupCarpet = _dereq_('../carpet/lookup_carpetid');
module.exports = function calc(gd, trace) {
var carpet = trace._carpetTrace = lookupCarpet(gd, trace);
if(!carpet || !carpet.visible || carpet.visible === 'legendonly') return;
var i;
// Transfer this over from carpet before plotting since this is a necessary
// condition in order for cartesian to actually plot this trace:
trace.xaxis = carpet.xaxis;
trace.yaxis = carpet.yaxis;
// make the calcdata array
var serieslen = trace._length;
var cd = new Array(serieslen);
var a, b;
var needsCull = false;
for(i = 0; i < serieslen; i++) {
a = trace.a[i];
b = trace.b[i];
if(isNumeric(a) && isNumeric(b)) {
var xy = carpet.ab2xy(+a, +b, true);
var visible = carpet.isVisible(+a, +b);
if(!visible) needsCull = true;
cd[i] = {x: xy[0], y: xy[1], a: a, b: b, vis: visible};
} else cd[i] = {x: false, y: false};
}
trace._needsCull = needsCull;
cd[0].carpet = carpet;
cd[0].trace = trace;
calcMarkerSize(trace, serieslen);
calcColorscale(gd, trace);
arraysToCalcdata(cd, trace);
calcSelection(cd, trace);
return cd;
};
},{"../carpet/lookup_carpetid":708,"../scatter/arrays_to_calcdata":924,"../scatter/calc":926,"../scatter/calc_selection":927,"../scatter/colorscale_calc":928,"fast-isnumeric":190}],961:[function(_dereq_,module,exports){
'use strict';
var Lib = _dereq_('../../lib');
var constants = _dereq_('../scatter/constants');
var subTypes = _dereq_('../scatter/subtypes');
var handleMarkerDefaults = _dereq_('../scatter/marker_defaults');
var handleLineDefaults = _dereq_('../scatter/line_defaults');
var handleLineShapeDefaults = _dereq_('../scatter/line_shape_defaults');
var handleTextDefaults = _dereq_('../scatter/text_defaults');
var handleFillColorDefaults = _dereq_('../scatter/fillcolor_defaults');
var attributes = _dereq_('./attributes');
module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout) {
function coerce(attr, dflt) {
return Lib.coerce(traceIn, traceOut, attributes, attr, dflt);
}
coerce('carpet');
// XXX: Don't hard code this
traceOut.xaxis = 'x';
traceOut.yaxis = 'y';
var a = coerce('a');
var b = coerce('b');
var len = Math.min(a.length, b.length);
if(!len) {
traceOut.visible = false;
return;
}
traceOut._length = len;
coerce('text');
coerce('texttemplate');
coerce('hovertext');
var defaultMode = len < constants.PTS_LINESONLY ? 'lines+markers' : 'lines';
coerce('mode', defaultMode);
if(subTypes.hasLines(traceOut)) {
handleLineDefaults(traceIn, traceOut, defaultColor, layout, coerce);
handleLineShapeDefaults(traceIn, traceOut, coerce);
coerce('connectgaps');
}
if(subTypes.hasMarkers(traceOut)) {
handleMarkerDefaults(traceIn, traceOut, defaultColor, layout, coerce, {gradient: true});
}
if(subTypes.hasText(traceOut)) {
handleTextDefaults(traceIn, traceOut, layout, coerce);
}
var dfltHoverOn = [];
if(subTypes.hasMarkers(traceOut) || subTypes.hasText(traceOut)) {
coerce('marker.maxdisplayed');
dfltHoverOn.push('points');
}
coerce('fill');
if(traceOut.fill !== 'none') {
handleFillColorDefaults(traceIn, traceOut, defaultColor, coerce);
if(!subTypes.hasLines(traceOut)) handleLineShapeDefaults(traceIn, traceOut, coerce);
}
if(traceOut.fill === 'tonext' || traceOut.fill === 'toself') {
dfltHoverOn.push('fills');
}
var hoverOn = coerce('hoveron', dfltHoverOn.join('+') || 'points');
if(hoverOn !== 'fills') coerce('hovertemplate');
Lib.coerceSelectionMarkerOpacity(traceOut, coerce);
};
},{"../../lib":503,"../scatter/constants":929,"../scatter/fillcolor_defaults":933,"../scatter/line_defaults":938,"../scatter/line_shape_defaults":940,"../scatter/marker_defaults":944,"../scatter/subtypes":950,"../scatter/text_defaults":951,"./attributes":959}],962:[function(_dereq_,module,exports){
'use strict';
module.exports = function eventData(out, pt, trace, cd, pointNumber) {
var cdi = cd[pointNumber];
out.a = cdi.a;
out.b = cdi.b;
out.y = cdi.y;
return out;
};
},{}],963:[function(_dereq_,module,exports){
'use strict';
module.exports = function formatLabels(cdi, trace) {
var labels = {};
var carpet = trace._carpet;
var ij = carpet.ab2ij([cdi.a, cdi.b]);
var i0 = Math.floor(ij[0]);
var ti = ij[0] - i0;
var j0 = Math.floor(ij[1]);
var tj = ij[1] - j0;
var xy = carpet.evalxy([], i0, j0, ti, tj);
labels.yLabel = xy[1].toFixed(3);
return labels;
};
},{}],964:[function(_dereq_,module,exports){
'use strict';
var scatterHover = _dereq_('../scatter/hover');
var fillText = _dereq_('../../lib').fillText;
module.exports = function hoverPoints(pointData, xval, yval, hovermode) {
var scatterPointData = scatterHover(pointData, xval, yval, hovermode);
if(!scatterPointData || scatterPointData[0].index === false) return;
var newPointData = scatterPointData[0];
// if hovering on a fill, we don't show any point data so the label is
// unchanged from what scatter gives us - except that it needs to
// be constrained to the trianglular plot area, not just the rectangular
// area defined by the synthetic x and y axes
// TODO: in some cases the vertical middle of the shape is not within
// the triangular viewport at all, so the label can become disconnected
// from the shape entirely. But calculating what portion of the shape
// is actually visible, as constrained by the diagonal axis lines, is not
// so easy and anyway we lost the information we would have needed to do
// this inside scatterHover.
if(newPointData.index === undefined) {
var yFracUp = 1 - (newPointData.y0 / pointData.ya._length);
var xLen = pointData.xa._length;
var xMin = xLen * yFracUp / 2;
var xMax = xLen - xMin;
newPointData.x0 = Math.max(Math.min(newPointData.x0, xMax), xMin);
newPointData.x1 = Math.max(Math.min(newPointData.x1, xMax), xMin);
return scatterPointData;
}
var cdi = newPointData.cd[newPointData.index];
newPointData.a = cdi.a;
newPointData.b = cdi.b;
newPointData.xLabelVal = undefined;
newPointData.yLabelVal = undefined;
// TODO: nice formatting, and label by axis title, for a, b, and c?
var trace = newPointData.trace;
var carpet = trace._carpet;
var labels = trace._module.formatLabels(cdi, trace);
newPointData.yLabel = labels.yLabel;
delete newPointData.text;
var text = [];
function textPart(ax, val) {
var prefix;
if(ax.labelprefix && ax.labelprefix.length > 0) {
prefix = ax.labelprefix.replace(/ = $/, '');
} else {
prefix = ax._hovertitle;
}
text.push(prefix + ': ' + val.toFixed(3) + ax.labelsuffix);
}
if(!trace.hovertemplate) {
var hoverinfo = cdi.hi || trace.hoverinfo;
var parts = hoverinfo.split('+');
if(parts.indexOf('all') !== -1) parts = ['a', 'b', 'text'];
if(parts.indexOf('a') !== -1) textPart(carpet.aaxis, cdi.a);
if(parts.indexOf('b') !== -1) textPart(carpet.baxis, cdi.b);
text.push('y: ' + newPointData.yLabel);
if(parts.indexOf('text') !== -1) {
fillText(cdi, trace, text);
}
newPointData.extraText = text.join('
');
}
return scatterPointData;
};
},{"../../lib":503,"../scatter/hover":936}],965:[function(_dereq_,module,exports){
'use strict';
module.exports = {
attributes: _dereq_('./attributes'),
supplyDefaults: _dereq_('./defaults'),
colorbar: _dereq_('../scatter/marker_colorbar'),
formatLabels: _dereq_('./format_labels'),
calc: _dereq_('./calc'),
plot: _dereq_('./plot'),
style: _dereq_('../scatter/style').style,
styleOnSelect: _dereq_('../scatter/style').styleOnSelect,
hoverPoints: _dereq_('./hover'),
selectPoints: _dereq_('../scatter/select'),
eventData: _dereq_('./event_data'),
moduleType: 'trace',
name: 'scattercarpet',
basePlotModule: _dereq_('../../plots/cartesian'),
categories: ['svg', 'carpet', 'symbols', 'showLegend', 'carpetDependent', 'zoomScale'],
meta: {
}
};
},{"../../plots/cartesian":568,"../scatter/marker_colorbar":943,"../scatter/select":947,"../scatter/style":949,"./attributes":959,"./calc":960,"./defaults":961,"./event_data":962,"./format_labels":963,"./hover":964,"./plot":966}],966:[function(_dereq_,module,exports){
'use strict';
var scatterPlot = _dereq_('../scatter/plot');
var Axes = _dereq_('../../plots/cartesian/axes');
var Drawing = _dereq_('../../components/drawing');
module.exports = function plot(gd, plotinfoproxy, data, layer) {
var i, trace, node;
var carpet = data[0][0].carpet;
// mimic cartesian plotinfo
var plotinfo = {
xaxis: Axes.getFromId(gd, carpet.xaxis || 'x'),
yaxis: Axes.getFromId(gd, carpet.yaxis || 'y'),
plot: plotinfoproxy.plot,
};
scatterPlot(gd, plotinfo, data, layer);
for(i = 0; i < data.length; i++) {
trace = data[i][0].trace;
// Note: .select is adequate but seems to mutate the node data,
// which is at least a bit surprising and causes problems elsewhere
node = layer.selectAll('g.trace' + trace.uid + ' .js-line');
// Note: it would be more efficient if this didn't need to be applied
// separately to all scattercarpet traces, but that would require
// lots of reorganization of scatter traces that is otherwise not
// necessary. That makes this a potential optimization.
Drawing.setClipUrl(node, data[i][0].carpet._clipPathId, gd);
}
};
},{"../../components/drawing":388,"../../plots/cartesian/axes":554,"../scatter/plot":946}],967:[function(_dereq_,module,exports){
'use strict';
var hovertemplateAttrs = _dereq_('../../plots/template_attributes').hovertemplateAttrs;
var texttemplateAttrs = _dereq_('../../plots/template_attributes').texttemplateAttrs;
var scatterAttrs = _dereq_('../scatter/attributes');
var baseAttrs = _dereq_('../../plots/attributes');
var colorAttributes = _dereq_('../../components/colorscale/attributes');
var dash = _dereq_('../../components/drawing/attributes').dash;
var extendFlat = _dereq_('../../lib/extend').extendFlat;
var overrideAll = _dereq_('../../plot_api/edit_types').overrideAll;
var scatterMarkerAttrs = scatterAttrs.marker;
var scatterLineAttrs = scatterAttrs.line;
var scatterMarkerLineAttrs = scatterMarkerAttrs.line;
module.exports = overrideAll({
lon: {
valType: 'data_array',
},
lat: {
valType: 'data_array',
},
locations: {
valType: 'data_array',
},
locationmode: {
valType: 'enumerated',
values: ['ISO-3', 'USA-states', 'country names', 'geojson-id'],
dflt: 'ISO-3',
},
geojson: {
valType: 'any',
editType: 'calc',
},
featureidkey: {
valType: 'string',
editType: 'calc',
dflt: 'id',
},
mode: extendFlat({}, scatterAttrs.mode, {dflt: 'markers'}),
text: extendFlat({}, scatterAttrs.text, {
}),
texttemplate: texttemplateAttrs({editType: 'plot'}, {
keys: ['lat', 'lon', 'location', 'text']
}),
hovertext: extendFlat({}, scatterAttrs.hovertext, {
}),
textfont: scatterAttrs.textfont,
textposition: scatterAttrs.textposition,
line: {
color: scatterLineAttrs.color,
width: scatterLineAttrs.width,
dash: dash
},
connectgaps: scatterAttrs.connectgaps,
marker: extendFlat({
symbol: scatterMarkerAttrs.symbol,
opacity: scatterMarkerAttrs.opacity,
size: scatterMarkerAttrs.size,
sizeref: scatterMarkerAttrs.sizeref,
sizemin: scatterMarkerAttrs.sizemin,
sizemode: scatterMarkerAttrs.sizemode,
colorbar: scatterMarkerAttrs.colorbar,
line: extendFlat({
width: scatterMarkerLineAttrs.width
},
colorAttributes('marker.line')
),
gradient: scatterMarkerAttrs.gradient
},
colorAttributes('marker')
),
fill: {
valType: 'enumerated',
values: ['none', 'toself'],
dflt: 'none',
},
fillcolor: scatterAttrs.fillcolor,
selected: scatterAttrs.selected,
unselected: scatterAttrs.unselected,
hoverinfo: extendFlat({}, baseAttrs.hoverinfo, {
flags: ['lon', 'lat', 'location', 'text', 'name']
}),
hovertemplate: hovertemplateAttrs(),
}, 'calc', 'nested');
},{"../../components/colorscale/attributes":373,"../../components/drawing/attributes":387,"../../lib/extend":493,"../../plot_api/edit_types":536,"../../plots/attributes":550,"../../plots/template_attributes":633,"../scatter/attributes":925}],968:[function(_dereq_,module,exports){
'use strict';
var isNumeric = _dereq_('fast-isnumeric');
var BADNUM = _dereq_('../../constants/numerical').BADNUM;
var calcMarkerColorscale = _dereq_('../scatter/colorscale_calc');
var arraysToCalcdata = _dereq_('../scatter/arrays_to_calcdata');
var calcSelection = _dereq_('../scatter/calc_selection');
var _ = _dereq_('../../lib')._;
function isNonBlankString(v) {
return v && typeof v === 'string';
}
module.exports = function calc(gd, trace) {
var hasLocationData = Array.isArray(trace.locations);
var len = hasLocationData ? trace.locations.length : trace._length;
var calcTrace = new Array(len);
var isValidLoc;
if(trace.geojson) {
isValidLoc = function(v) { return isNonBlankString(v) || isNumeric(v); };
} else {
isValidLoc = isNonBlankString;
}
for(var i = 0; i < len; i++) {
var calcPt = calcTrace[i] = {};
if(hasLocationData) {
var loc = trace.locations[i];
calcPt.loc = isValidLoc(loc) ? loc : null;
} else {
var lon = trace.lon[i];
var lat = trace.lat[i];
if(isNumeric(lon) && isNumeric(lat)) calcPt.lonlat = [+lon, +lat];
else calcPt.lonlat = [BADNUM, BADNUM];
}
}
arraysToCalcdata(calcTrace, trace);
calcMarkerColorscale(gd, trace);
calcSelection(calcTrace, trace);
if(len) {
calcTrace[0].t = {
labels: {
lat: _(gd, 'lat:') + ' ',
lon: _(gd, 'lon:') + ' '
}
};
}
return calcTrace;
};
},{"../../constants/numerical":479,"../../lib":503,"../scatter/arrays_to_calcdata":924,"../scatter/calc_selection":927,"../scatter/colorscale_calc":928,"fast-isnumeric":190}],969:[function(_dereq_,module,exports){
'use strict';
var Lib = _dereq_('../../lib');
var subTypes = _dereq_('../scatter/subtypes');
var handleMarkerDefaults = _dereq_('../scatter/marker_defaults');
var handleLineDefaults = _dereq_('../scatter/line_defaults');
var handleTextDefaults = _dereq_('../scatter/text_defaults');
var handleFillColorDefaults = _dereq_('../scatter/fillcolor_defaults');
var attributes = _dereq_('./attributes');
module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout) {
function coerce(attr, dflt) {
return Lib.coerce(traceIn, traceOut, attributes, attr, dflt);
}
var locations = coerce('locations');
var len;
if(locations && locations.length) {
var geojson = coerce('geojson');
var locationmodeDflt;
if((typeof geojson === 'string' && geojson !== '') || Lib.isPlainObject(geojson)) {
locationmodeDflt = 'geojson-id';
}
var locationMode = coerce('locationmode', locationmodeDflt);
if(locationMode === 'geojson-id') {
coerce('featureidkey');
}
len = locations.length;
} else {
var lon = coerce('lon') || [];
var lat = coerce('lat') || [];
len = Math.min(lon.length, lat.length);
}
if(!len) {
traceOut.visible = false;
return;
}
traceOut._length = len;
coerce('text');
coerce('hovertext');
coerce('hovertemplate');
coerce('mode');
if(subTypes.hasLines(traceOut)) {
handleLineDefaults(traceIn, traceOut, defaultColor, layout, coerce);
coerce('connectgaps');
}
if(subTypes.hasMarkers(traceOut)) {
handleMarkerDefaults(traceIn, traceOut, defaultColor, layout, coerce, {gradient: true});
}
if(subTypes.hasText(traceOut)) {
coerce('texttemplate');
handleTextDefaults(traceIn, traceOut, layout, coerce);
}
coerce('fill');
if(traceOut.fill !== 'none') {
handleFillColorDefaults(traceIn, traceOut, defaultColor, coerce);
}
Lib.coerceSelectionMarkerOpacity(traceOut, coerce);
};
},{"../../lib":503,"../scatter/fillcolor_defaults":933,"../scatter/line_defaults":938,"../scatter/marker_defaults":944,"../scatter/subtypes":950,"../scatter/text_defaults":951,"./attributes":967}],970:[function(_dereq_,module,exports){
'use strict';
module.exports = function eventData(out, pt, trace, cd, pointNumber) {
out.lon = pt.lon;
out.lat = pt.lat;
out.location = pt.loc ? pt.loc : null;
// include feature properties from input geojson
var cdi = cd[pointNumber];
if(cdi.fIn && cdi.fIn.properties) {
out.properties = cdi.fIn.properties;
}
return out;
};
},{}],971:[function(_dereq_,module,exports){
'use strict';
var Axes = _dereq_('../../plots/cartesian/axes');
module.exports = function formatLabels(cdi, trace, fullLayout) {
var labels = {};
var geo = fullLayout[trace.geo]._subplot;
var ax = geo.mockAxis;
var lonlat = cdi.lonlat;
labels.lonLabel = Axes.tickText(ax, ax.c2l(lonlat[0]), true).text;
labels.latLabel = Axes.tickText(ax, ax.c2l(lonlat[1]), true).text;
return labels;
};
},{"../../plots/cartesian/axes":554}],972:[function(_dereq_,module,exports){
'use strict';
var Fx = _dereq_('../../components/fx');
var BADNUM = _dereq_('../../constants/numerical').BADNUM;
var getTraceColor = _dereq_('../scatter/get_trace_color');
var fillText = _dereq_('../../lib').fillText;
var attributes = _dereq_('./attributes');
module.exports = function hoverPoints(pointData, xval, yval) {
var cd = pointData.cd;
var trace = cd[0].trace;
var xa = pointData.xa;
var ya = pointData.ya;
var geo = pointData.subplot;
var isLonLatOverEdges = geo.projection.isLonLatOverEdges;
var project = geo.project;
function distFn(d) {
var lonlat = d.lonlat;
if(lonlat[0] === BADNUM) return Infinity;
if(isLonLatOverEdges(lonlat)) return Infinity;
var pt = project(lonlat);
var px = project([xval, yval]);
var dx = Math.abs(pt[0] - px[0]);
var dy = Math.abs(pt[1] - px[1]);
var rad = Math.max(3, d.mrc || 0);
// N.B. d.mrc is the calculated marker radius
// which is only set for trace with 'markers' mode.
return Math.max(Math.sqrt(dx * dx + dy * dy) - rad, 1 - 3 / rad);
}
Fx.getClosest(cd, distFn, pointData);
// skip the rest (for this trace) if we didn't find a close point
if(pointData.index === false) return;
var di = cd[pointData.index];
var lonlat = di.lonlat;
var pos = [xa.c2p(lonlat), ya.c2p(lonlat)];
var rad = di.mrc || 1;
pointData.x0 = pos[0] - rad;
pointData.x1 = pos[0] + rad;
pointData.y0 = pos[1] - rad;
pointData.y1 = pos[1] + rad;
pointData.loc = di.loc;
pointData.lon = lonlat[0];
pointData.lat = lonlat[1];
var fullLayout = {};
fullLayout[trace.geo] = {_subplot: geo};
var labels = trace._module.formatLabels(di, trace, fullLayout);
pointData.lonLabel = labels.lonLabel;
pointData.latLabel = labels.latLabel;
pointData.color = getTraceColor(trace, di);
pointData.extraText = getExtraText(trace, di, pointData, cd[0].t.labels);
pointData.hovertemplate = trace.hovertemplate;
return [pointData];
};
function getExtraText(trace, pt, pointData, labels) {
if(trace.hovertemplate) return;
var hoverinfo = pt.hi || trace.hoverinfo;
var parts = hoverinfo === 'all' ?
attributes.hoverinfo.flags :
hoverinfo.split('+');
var hasLocation = parts.indexOf('location') !== -1 && Array.isArray(trace.locations);
var hasLon = (parts.indexOf('lon') !== -1);
var hasLat = (parts.indexOf('lat') !== -1);
var hasText = (parts.indexOf('text') !== -1);
var text = [];
function format(val) { return val + '\u00B0'; }
if(hasLocation) {
text.push(pt.loc);
} else if(hasLon && hasLat) {
text.push('(' + format(pointData.latLabel) + ', ' + format(pointData.lonLabel) + ')');
} else if(hasLon) {
text.push(labels.lon + format(pointData.lonLabel));
} else if(hasLat) {
text.push(labels.lat + format(pointData.latLabel));
}
if(hasText) {
fillText(pt, trace, text);
}
return text.join('
');
}
},{"../../components/fx":406,"../../constants/numerical":479,"../../lib":503,"../scatter/get_trace_color":935,"./attributes":967}],973:[function(_dereq_,module,exports){
'use strict';
module.exports = {
attributes: _dereq_('./attributes'),
supplyDefaults: _dereq_('./defaults'),
colorbar: _dereq_('../scatter/marker_colorbar'),
formatLabels: _dereq_('./format_labels'),
calc: _dereq_('./calc'),
calcGeoJSON: _dereq_('./plot').calcGeoJSON,
plot: _dereq_('./plot').plot,
style: _dereq_('./style'),
styleOnSelect: _dereq_('../scatter/style').styleOnSelect,
hoverPoints: _dereq_('./hover'),
eventData: _dereq_('./event_data'),
selectPoints: _dereq_('./select'),
moduleType: 'trace',
name: 'scattergeo',
basePlotModule: _dereq_('../../plots/geo'),
categories: ['geo', 'symbols', 'showLegend', 'scatter-like'],
meta: {
}
};
},{"../../plots/geo":589,"../scatter/marker_colorbar":943,"../scatter/style":949,"./attributes":967,"./calc":968,"./defaults":969,"./event_data":970,"./format_labels":971,"./hover":972,"./plot":974,"./select":975,"./style":976}],974:[function(_dereq_,module,exports){
'use strict';
var d3 = _dereq_('@plotly/d3');
var Lib = _dereq_('../../lib');
var getTopojsonFeatures = _dereq_('../../lib/topojson_utils').getTopojsonFeatures;
var geoJsonUtils = _dereq_('../../lib/geojson_utils');
var geoUtils = _dereq_('../../lib/geo_location_utils');
var findExtremes = _dereq_('../../plots/cartesian/autorange').findExtremes;
var BADNUM = _dereq_('../../constants/numerical').BADNUM;
var calcMarkerSize = _dereq_('../scatter/calc').calcMarkerSize;
var subTypes = _dereq_('../scatter/subtypes');
var style = _dereq_('./style');
function plot(gd, geo, calcData) {
var scatterLayer = geo.layers.frontplot.select('.scatterlayer');
var gTraces = Lib.makeTraceGroups(scatterLayer, calcData, 'trace scattergeo');
function removeBADNUM(d, node) {
if(d.lonlat[0] === BADNUM) {
d3.select(node).remove();
}
}
// TODO find a way to order the inner nodes on update
gTraces.selectAll('*').remove();
gTraces.each(function(calcTrace) {
var s = d3.select(this);
var trace = calcTrace[0].trace;
if(subTypes.hasLines(trace) || trace.fill !== 'none') {
var lineCoords = geoJsonUtils.calcTraceToLineCoords(calcTrace);
var lineData = (trace.fill !== 'none') ?
geoJsonUtils.makePolygon(lineCoords) :
geoJsonUtils.makeLine(lineCoords);
s.selectAll('path.js-line')
.data([{geojson: lineData, trace: trace}])
.enter().append('path')
.classed('js-line', true)
.style('stroke-miterlimit', 2);
}
if(subTypes.hasMarkers(trace)) {
s.selectAll('path.point')
.data(Lib.identity)
.enter().append('path')
.classed('point', true)
.each(function(calcPt) { removeBADNUM(calcPt, this); });
}
if(subTypes.hasText(trace)) {
s.selectAll('g')
.data(Lib.identity)
.enter().append('g')
.append('text')
.each(function(calcPt) { removeBADNUM(calcPt, this); });
}
// call style here within topojson request callback
style(gd, calcTrace);
});
}
function calcGeoJSON(calcTrace, fullLayout) {
var trace = calcTrace[0].trace;
var geoLayout = fullLayout[trace.geo];
var geo = geoLayout._subplot;
var len = trace._length;
var i, calcPt;
if(Array.isArray(trace.locations)) {
var locationmode = trace.locationmode;
var features = locationmode === 'geojson-id' ?
geoUtils.extractTraceFeature(calcTrace) :
getTopojsonFeatures(trace, geo.topojson);
for(i = 0; i < len; i++) {
calcPt = calcTrace[i];
var feature = locationmode === 'geojson-id' ?
calcPt.fOut :
geoUtils.locationToFeature(locationmode, calcPt.loc, features);
calcPt.lonlat = feature ? feature.properties.ct : [BADNUM, BADNUM];
}
}
var opts = {padded: true};
var lonArray;
var latArray;
if(geoLayout.fitbounds === 'geojson' && trace.locationmode === 'geojson-id') {
var bboxGeojson = geoUtils.computeBbox(geoUtils.getTraceGeojson(trace));
lonArray = [bboxGeojson[0], bboxGeojson[2]];
latArray = [bboxGeojson[1], bboxGeojson[3]];
} else {
lonArray = new Array(len);
latArray = new Array(len);
for(i = 0; i < len; i++) {
calcPt = calcTrace[i];
lonArray[i] = calcPt.lonlat[0];
latArray[i] = calcPt.lonlat[1];
}
opts.ppad = calcMarkerSize(trace, len);
}
trace._extremes.lon = findExtremes(geoLayout.lonaxis._ax, lonArray, opts);
trace._extremes.lat = findExtremes(geoLayout.lataxis._ax, latArray, opts);
}
module.exports = {
calcGeoJSON: calcGeoJSON,
plot: plot
};
},{"../../constants/numerical":479,"../../lib":503,"../../lib/geo_location_utils":496,"../../lib/geojson_utils":497,"../../lib/topojson_utils":532,"../../plots/cartesian/autorange":553,"../scatter/calc":926,"../scatter/subtypes":950,"./style":976,"@plotly/d3":58}],975:[function(_dereq_,module,exports){
'use strict';
var subtypes = _dereq_('../scatter/subtypes');
var BADNUM = _dereq_('../../constants/numerical').BADNUM;
module.exports = function selectPoints(searchInfo, selectionTester) {
var cd = searchInfo.cd;
var xa = searchInfo.xaxis;
var ya = searchInfo.yaxis;
var selection = [];
var trace = cd[0].trace;
var di, lonlat, x, y, i;
var hasOnlyLines = (!subtypes.hasMarkers(trace) && !subtypes.hasText(trace));
if(hasOnlyLines) return [];
if(selectionTester === false) {
for(i = 0; i < cd.length; i++) {
cd[i].selected = 0;
}
} else {
for(i = 0; i < cd.length; i++) {
di = cd[i];
lonlat = di.lonlat;
// some projection types can't handle BADNUMs
if(lonlat[0] === BADNUM) continue;
x = xa.c2p(lonlat);
y = ya.c2p(lonlat);
if(selectionTester.contains([x, y], null, i, searchInfo)) {
selection.push({
pointNumber: i,
lon: lonlat[0],
lat: lonlat[1]
});
di.selected = 1;
} else {
di.selected = 0;
}
}
}
return selection;
};
},{"../../constants/numerical":479,"../scatter/subtypes":950}],976:[function(_dereq_,module,exports){
'use strict';
var d3 = _dereq_('@plotly/d3');
var Drawing = _dereq_('../../components/drawing');
var Color = _dereq_('../../components/color');
var scatterStyle = _dereq_('../scatter/style');
var stylePoints = scatterStyle.stylePoints;
var styleText = scatterStyle.styleText;
module.exports = function style(gd, calcTrace) {
if(calcTrace) styleTrace(gd, calcTrace);
};
function styleTrace(gd, calcTrace) {
var trace = calcTrace[0].trace;
var s = calcTrace[0].node3;
s.style('opacity', calcTrace[0].trace.opacity);
stylePoints(s, trace, gd);
styleText(s, trace, gd);
// this part is incompatible with Drawing.lineGroupStyle
s.selectAll('path.js-line')
.style('fill', 'none')
.each(function(d) {
var path = d3.select(this);
var trace = d.trace;
var line = trace.line || {};
path.call(Color.stroke, line.color)
.call(Drawing.dashLine, line.dash || '', line.width || 0);
if(trace.fill !== 'none') {
path.call(Color.fill, trace.fillcolor);
}
});
}
},{"../../components/color":366,"../../components/drawing":388,"../scatter/style":949,"@plotly/d3":58}],977:[function(_dereq_,module,exports){
'use strict';
var baseAttrs = _dereq_('../../plots/attributes');
var scatterAttrs = _dereq_('../scatter/attributes');
var axisHoverFormat = _dereq_('../../plots/cartesian/axis_format_attributes').axisHoverFormat;
var colorScaleAttrs = _dereq_('../../components/colorscale/attributes');
var sortObjectKeys = _dereq_('../../lib/sort_object_keys');
var extendFlat = _dereq_('../../lib/extend').extendFlat;
var overrideAll = _dereq_('../../plot_api/edit_types').overrideAll;
var DASHES = _dereq_('./constants').DASHES;
var scatterLineAttrs = scatterAttrs.line;
var scatterMarkerAttrs = scatterAttrs.marker;
var scatterMarkerLineAttrs = scatterMarkerAttrs.line;
var attrs = module.exports = overrideAll({
x: scatterAttrs.x,
x0: scatterAttrs.x0,
dx: scatterAttrs.dx,
y: scatterAttrs.y,
y0: scatterAttrs.y0,
dy: scatterAttrs.dy,
xperiod: scatterAttrs.xperiod,
yperiod: scatterAttrs.yperiod,
xperiod0: scatterAttrs.xperiod0,
yperiod0: scatterAttrs.yperiod0,
xperiodalignment: scatterAttrs.xperiodalignment,
yperiodalignment: scatterAttrs.yperiodalignment,
xhoverformat: axisHoverFormat('x'),
yhoverformat: axisHoverFormat('y'),
text: scatterAttrs.text,
hovertext: scatterAttrs.hovertext,
textposition: scatterAttrs.textposition,
textfont: scatterAttrs.textfont,
mode: {
valType: 'flaglist',
flags: ['lines', 'markers', 'text'],
extras: ['none'],
},
line: {
color: scatterLineAttrs.color,
width: scatterLineAttrs.width,
shape: {
valType: 'enumerated',
values: ['linear', 'hv', 'vh', 'hvh', 'vhv'],
dflt: 'linear',
editType: 'plot',
},
dash: {
valType: 'enumerated',
values: sortObjectKeys(DASHES),
dflt: 'solid',
}
},
marker: extendFlat({}, colorScaleAttrs('marker'), {
symbol: scatterMarkerAttrs.symbol,
size: scatterMarkerAttrs.size,
sizeref: scatterMarkerAttrs.sizeref,
sizemin: scatterMarkerAttrs.sizemin,
sizemode: scatterMarkerAttrs.sizemode,
opacity: scatterMarkerAttrs.opacity,
colorbar: scatterMarkerAttrs.colorbar,
line: extendFlat({}, colorScaleAttrs('marker.line'), {
width: scatterMarkerLineAttrs.width
})
}),
connectgaps: scatterAttrs.connectgaps,
fill: extendFlat({}, scatterAttrs.fill, {dflt: 'none'}),
fillcolor: scatterAttrs.fillcolor,
// no hoveron
selected: {
marker: scatterAttrs.selected.marker,
textfont: scatterAttrs.selected.textfont
},
unselected: {
marker: scatterAttrs.unselected.marker,
textfont: scatterAttrs.unselected.textfont
},
opacity: baseAttrs.opacity
}, 'calc', 'nested');
attrs.x.editType = attrs.y.editType = attrs.x0.editType = attrs.y0.editType = 'calc+clearAxisTypes';
attrs.hovertemplate = scatterAttrs.hovertemplate;
attrs.texttemplate = scatterAttrs.texttemplate;
},{"../../components/colorscale/attributes":373,"../../lib/extend":493,"../../lib/sort_object_keys":526,"../../plot_api/edit_types":536,"../../plots/attributes":550,"../../plots/cartesian/axis_format_attributes":557,"../scatter/attributes":925,"./constants":979}],978:[function(_dereq_,module,exports){
'use strict';
var cluster = _dereq_('@plotly/point-cluster');
var Lib = _dereq_('../../lib');
var AxisIDs = _dereq_('../../plots/cartesian/axis_ids');
var findExtremes = _dereq_('../../plots/cartesian/autorange').findExtremes;
var alignPeriod = _dereq_('../../plots/cartesian/align_period');
var scatterCalc = _dereq_('../scatter/calc');
var calcMarkerSize = scatterCalc.calcMarkerSize;
var calcAxisExpansion = scatterCalc.calcAxisExpansion;
var setFirstScatter = scatterCalc.setFirstScatter;
var calcColorscale = _dereq_('../scatter/colorscale_calc');
var convert = _dereq_('./convert');
var sceneUpdate = _dereq_('./scene_update');
var BADNUM = _dereq_('../../constants/numerical').BADNUM;
var TOO_MANY_POINTS = _dereq_('./constants').TOO_MANY_POINTS;
module.exports = function calc(gd, trace) {
var fullLayout = gd._fullLayout;
var xa = AxisIDs.getFromId(gd, trace.xaxis);
var ya = AxisIDs.getFromId(gd, trace.yaxis);
var subplot = fullLayout._plots[trace.xaxis + trace.yaxis];
var len = trace._length;
var hasTooManyPoints = len >= TOO_MANY_POINTS;
var len2 = len * 2;
var stash = {};
var i;
var origX = xa.makeCalcdata(trace, 'x');
var origY = ya.makeCalcdata(trace, 'y');
var xObj = alignPeriod(trace, xa, 'x', origX);
var yObj = alignPeriod(trace, ya, 'y', origY);
var x = xObj.vals;
var y = yObj.vals;
trace._x = x;
trace._y = y;
if(trace.xperiodalignment) {
trace._origX = origX;
trace._xStarts = xObj.starts;
trace._xEnds = xObj.ends;
}
if(trace.yperiodalignment) {
trace._origY = origY;
trace._yStarts = yObj.starts;
trace._yEnds = yObj.ends;
}
// we need hi-precision for scatter2d,
// regl-scatter2d uses NaNs for bad/missing values
var positions = new Array(len2);
var _ids = new Array(len);
for(i = 0; i < len; i++) {
positions[i * 2] = x[i] === BADNUM ? NaN : x[i];
positions[i * 2 + 1] = y[i] === BADNUM ? NaN : y[i];
// Pre-compute ids.
_ids[i] = i;
}
if(xa.type === 'log') {
for(i = 0; i < len2; i += 2) {
positions[i] = xa.c2l(positions[i]);
}
}
if(ya.type === 'log') {
for(i = 1; i < len2; i += 2) {
positions[i] = ya.c2l(positions[i]);
}
}
// we don't build a tree for log axes since it takes long to convert log2px
// and it is also
if(hasTooManyPoints && (xa.type !== 'log' && ya.type !== 'log')) {
// FIXME: delegate this to webworker
stash.tree = cluster(positions);
} else {
stash.ids = _ids;
}
// create scene options and scene
calcColorscale(gd, trace);
var opts = sceneOptions(gd, subplot, trace, positions, x, y);
var scene = sceneUpdate(gd, subplot);
// Reuse SVG scatter axis expansion routine.
// For graphs with very large number of points and array marker.size,
// use average marker size instead to speed things up.
setFirstScatter(fullLayout, trace);
var ppad;
if(!hasTooManyPoints) {
ppad = calcMarkerSize(trace, len);
} else if(opts.marker) {
ppad = opts.marker.sizeAvg || Math.max(opts.marker.size, 3);
}
calcAxisExpansion(gd, trace, xa, ya, x, y, ppad);
if(opts.errorX) expandForErrorBars(trace, xa, opts.errorX);
if(opts.errorY) expandForErrorBars(trace, ya, opts.errorY);
// set flags to create scene renderers
if(opts.fill && !scene.fill2d) scene.fill2d = true;
if(opts.marker && !scene.scatter2d) scene.scatter2d = true;
if(opts.line && !scene.line2d) scene.line2d = true;
if((opts.errorX || opts.errorY) && !scene.error2d) scene.error2d = true;
if(opts.text && !scene.glText) scene.glText = true;
if(opts.marker) opts.marker.snap = len;
scene.lineOptions.push(opts.line);
scene.errorXOptions.push(opts.errorX);
scene.errorYOptions.push(opts.errorY);
scene.fillOptions.push(opts.fill);
scene.markerOptions.push(opts.marker);
scene.markerSelectedOptions.push(opts.markerSel);
scene.markerUnselectedOptions.push(opts.markerUnsel);
scene.textOptions.push(opts.text);
scene.textSelectedOptions.push(opts.textSel);
scene.textUnselectedOptions.push(opts.textUnsel);
scene.selectBatch.push([]);
scene.unselectBatch.push([]);
stash._scene = scene;
stash.index = scene.count;
stash.x = x;
stash.y = y;
stash.positions = positions;
scene.count++;
return [{x: false, y: false, t: stash, trace: trace}];
};
function expandForErrorBars(trace, ax, opts) {
var extremes = trace._extremes[ax._id];
var errExt = findExtremes(ax, opts._bnds, {padded: true});
extremes.min = extremes.min.concat(errExt.min);
extremes.max = extremes.max.concat(errExt.max);
}
function sceneOptions(gd, subplot, trace, positions, x, y) {
var opts = convert.style(gd, trace);
if(opts.marker) {
opts.marker.positions = positions;
}
if(opts.line && positions.length > 1) {
Lib.extendFlat(
opts.line,
convert.linePositions(gd, trace, positions)
);
}
if(opts.errorX || opts.errorY) {
var errors = convert.errorBarPositions(gd, trace, positions, x, y);
if(opts.errorX) {
Lib.extendFlat(opts.errorX, errors.x);
}
if(opts.errorY) {
Lib.extendFlat(opts.errorY, errors.y);
}
}
if(opts.text) {
Lib.extendFlat(
opts.text,
{positions: positions},
convert.textPosition(gd, trace, opts.text, opts.marker)
);
Lib.extendFlat(
opts.textSel,
{positions: positions},
convert.textPosition(gd, trace, opts.text, opts.markerSel)
);
Lib.extendFlat(
opts.textUnsel,
{positions: positions},
convert.textPosition(gd, trace, opts.text, opts.markerUnsel)
);
}
return opts;
}
},{"../../constants/numerical":479,"../../lib":503,"../../plots/cartesian/align_period":551,"../../plots/cartesian/autorange":553,"../../plots/cartesian/axis_ids":558,"../scatter/calc":926,"../scatter/colorscale_calc":928,"./constants":979,"./convert":980,"./scene_update":988,"@plotly/point-cluster":59}],979:[function(_dereq_,module,exports){
'use strict';
var SYMBOL_SIZE = 20;
module.exports = {
TOO_MANY_POINTS: 1e5,
SYMBOL_SDF_SIZE: 200,
SYMBOL_SIZE: SYMBOL_SIZE,
SYMBOL_STROKE: SYMBOL_SIZE / 20,
DOT_RE: /-dot/,
OPEN_RE: /-open/,
DASHES: {
solid: [1],
dot: [1, 1],
dash: [4, 1],
longdash: [8, 1],
dashdot: [4, 1, 1, 1],
longdashdot: [8, 1, 1, 1]
}
};
},{}],980:[function(_dereq_,module,exports){
'use strict';
var isNumeric = _dereq_('fast-isnumeric');
var svgSdf = _dereq_('svg-path-sdf');
var rgba = _dereq_('color-normalize');
var Registry = _dereq_('../../registry');
var Lib = _dereq_('../../lib');
var Drawing = _dereq_('../../components/drawing');
var AxisIDs = _dereq_('../../plots/cartesian/axis_ids');
var formatColor = _dereq_('../../lib/gl_format_color').formatColor;
var subTypes = _dereq_('../scatter/subtypes');
var makeBubbleSizeFn = _dereq_('../scatter/make_bubble_size_func');
var helpers = _dereq_('./helpers');
var constants = _dereq_('./constants');
var DESELECTDIM = _dereq_('../../constants/interactions').DESELECTDIM;
var TEXTOFFSETSIGN = {
start: 1, left: 1, end: -1, right: -1, middle: 0, center: 0, bottom: 1, top: -1
};
var appendArrayPointValue = _dereq_('../../components/fx/helpers').appendArrayPointValue;
function convertStyle(gd, trace) {
var i;
var opts = {
marker: undefined,
markerSel: undefined,
markerUnsel: undefined,
line: undefined,
fill: undefined,
errorX: undefined,
errorY: undefined,
text: undefined,
textSel: undefined,
textUnsel: undefined
};
var plotGlPixelRatio = gd._context.plotGlPixelRatio;
if(trace.visible !== true) return opts;
if(subTypes.hasText(trace)) {
opts.text = convertTextStyle(gd, trace);
opts.textSel = convertTextSelection(gd, trace, trace.selected);
opts.textUnsel = convertTextSelection(gd, trace, trace.unselected);
}
if(subTypes.hasMarkers(trace)) {
opts.marker = convertMarkerStyle(trace);
opts.markerSel = convertMarkerSelection(trace, trace.selected);
opts.markerUnsel = convertMarkerSelection(trace, trace.unselected);
if(!trace.unselected && Lib.isArrayOrTypedArray(trace.marker.opacity)) {
var mo = trace.marker.opacity;
opts.markerUnsel.opacity = new Array(mo.length);
for(i = 0; i < mo.length; i++) {
opts.markerUnsel.opacity[i] = DESELECTDIM * mo[i];
}
}
}
if(subTypes.hasLines(trace)) {
opts.line = {
overlay: true,
thickness: trace.line.width * plotGlPixelRatio,
color: trace.line.color,
opacity: trace.opacity
};
var dashes = (constants.DASHES[trace.line.dash] || [1]).slice();
for(i = 0; i < dashes.length; ++i) {
dashes[i] *= trace.line.width * plotGlPixelRatio;
}
opts.line.dashes = dashes;
}
if(trace.error_x && trace.error_x.visible) {
opts.errorX = convertErrorBarStyle(trace, trace.error_x, plotGlPixelRatio);
}
if(trace.error_y && trace.error_y.visible) {
opts.errorY = convertErrorBarStyle(trace, trace.error_y, plotGlPixelRatio);
}
if(!!trace.fill && trace.fill !== 'none') {
opts.fill = {
closed: true,
fill: trace.fillcolor,
thickness: 0
};
}
return opts;
}
function convertTextStyle(gd, trace) {
var fullLayout = gd._fullLayout;
var count = trace._length;
var textfontIn = trace.textfont;
var textpositionIn = trace.textposition;
var textPos = Array.isArray(textpositionIn) ? textpositionIn : [textpositionIn];
var tfc = textfontIn.color;
var tfs = textfontIn.size;
var tff = textfontIn.family;
var optsOut = {};
var i;
var plotGlPixelRatio = gd._context.plotGlPixelRatio;
var texttemplate = trace.texttemplate;
if(texttemplate) {
optsOut.text = [];
var d3locale = fullLayout._d3locale;
var isArray = Array.isArray(texttemplate);
var N = isArray ? Math.min(texttemplate.length, count) : count;
var txt = isArray ?
function(i) { return texttemplate[i]; } :
function() { return texttemplate; };
for(i = 0; i < N; i++) {
var d = {i: i};
var labels = trace._module.formatLabels(d, trace, fullLayout);
var pointValues = {};
appendArrayPointValue(pointValues, trace, i);
var meta = trace._meta || {};
optsOut.text.push(Lib.texttemplateString(txt(i), labels, d3locale, pointValues, d, meta));
}
} else {
if(Array.isArray(trace.text) && trace.text.length < count) {
// if text array is shorter, we'll need to append to it, so let's slice to prevent mutating
optsOut.text = trace.text.slice();
} else {
optsOut.text = trace.text;
}
}
// pad text array with empty strings
if(Array.isArray(optsOut.text)) {
for(i = optsOut.text.length; i < count; i++) {
optsOut.text[i] = '';
}
}
optsOut.opacity = trace.opacity;
optsOut.font = {};
optsOut.align = [];
optsOut.baseline = [];
for(i = 0; i < textPos.length; i++) {
var tp = textPos[i].split(/\s+/);
switch(tp[1]) {
case 'left':
optsOut.align.push('right');
break;
case 'right':
optsOut.align.push('left');
break;
default:
optsOut.align.push(tp[1]);
}
switch(tp[0]) {
case 'top':
optsOut.baseline.push('bottom');
break;
case 'bottom':
optsOut.baseline.push('top');
break;
default:
optsOut.baseline.push(tp[0]);
}
}
if(Array.isArray(tfc)) {
optsOut.color = new Array(count);
for(i = 0; i < count; i++) {
optsOut.color[i] = tfc[i];
}
} else {
optsOut.color = tfc;
}
if(Lib.isArrayOrTypedArray(tfs) || Array.isArray(tff)) {
// if any textfont param is array - make render a batch
optsOut.font = new Array(count);
for(i = 0; i < count; i++) {
var fonti = optsOut.font[i] = {};
fonti.size = (
Lib.isTypedArray(tfs) ? tfs[i] :
Array.isArray(tfs) ? (
isNumeric(tfs[i]) ? tfs[i] : 0
) : tfs
) * plotGlPixelRatio;
fonti.family = Array.isArray(tff) ? tff[i] : tff;
}
} else {
// if both are single values, make render fast single-value
optsOut.font = {size: tfs * plotGlPixelRatio, family: tff};
}
return optsOut;
}
function convertMarkerStyle(trace) {
var count = trace._length;
var optsIn = trace.marker;
var optsOut = {};
var i;
var multiSymbol = Lib.isArrayOrTypedArray(optsIn.symbol);
var multiColor = Lib.isArrayOrTypedArray(optsIn.color);
var multiLineColor = Lib.isArrayOrTypedArray(optsIn.line.color);
var multiOpacity = Lib.isArrayOrTypedArray(optsIn.opacity);
var multiSize = Lib.isArrayOrTypedArray(optsIn.size);
var multiLineWidth = Lib.isArrayOrTypedArray(optsIn.line.width);
var isOpen;
if(!multiSymbol) isOpen = helpers.isOpenSymbol(optsIn.symbol);
// prepare colors
if(multiSymbol || multiColor || multiLineColor || multiOpacity) {
optsOut.colors = new Array(count);
optsOut.borderColors = new Array(count);
var colors = formatColor(optsIn, optsIn.opacity, count);
var borderColors = formatColor(optsIn.line, optsIn.opacity, count);
if(!Array.isArray(borderColors[0])) {
var borderColor = borderColors;
borderColors = Array(count);
for(i = 0; i < count; i++) {
borderColors[i] = borderColor;
}
}
if(!Array.isArray(colors[0])) {
var color = colors;
colors = Array(count);
for(i = 0; i < count; i++) {
colors[i] = color;
}
}
optsOut.colors = colors;
optsOut.borderColors = borderColors;
for(i = 0; i < count; i++) {
if(multiSymbol) {
var symbol = optsIn.symbol[i];
isOpen = helpers.isOpenSymbol(symbol);
}
if(isOpen) {
borderColors[i] = colors[i].slice();
colors[i] = colors[i].slice();
colors[i][3] = 0;
}
}
optsOut.opacity = trace.opacity;
} else {
if(isOpen) {
optsOut.color = rgba(optsIn.color, 'uint8');
optsOut.color[3] = 0;
optsOut.borderColor = rgba(optsIn.color, 'uint8');
} else {
optsOut.color = rgba(optsIn.color, 'uint8');
optsOut.borderColor = rgba(optsIn.line.color, 'uint8');
}
optsOut.opacity = trace.opacity * optsIn.opacity;
}
// prepare symbols
if(multiSymbol) {
optsOut.markers = new Array(count);
for(i = 0; i < count; i++) {
optsOut.markers[i] = getSymbolSdf(optsIn.symbol[i]);
}
} else {
optsOut.marker = getSymbolSdf(optsIn.symbol);
}
// prepare sizes
var sizeFactor = 1;
var markerSizeFunc = makeBubbleSizeFn(trace, sizeFactor);
var s;
if(multiSize || multiLineWidth) {
var sizes = optsOut.sizes = new Array(count);
var borderSizes = optsOut.borderSizes = new Array(count);
var sizeTotal = 0;
var sizeAvg;
if(multiSize) {
for(i = 0; i < count; i++) {
sizes[i] = markerSizeFunc(optsIn.size[i]);
sizeTotal += sizes[i];
}
sizeAvg = sizeTotal / count;
} else {
s = markerSizeFunc(optsIn.size);
for(i = 0; i < count; i++) {
sizes[i] = s;
}
}
// See https://github.com/plotly/plotly.js/pull/1781#discussion_r121820798
if(multiLineWidth) {
for(i = 0; i < count; i++) {
borderSizes[i] = optsIn.line.width[i];
}
} else {
s = optsIn.line.width;
for(i = 0; i < count; i++) {
borderSizes[i] = s;
}
}
optsOut.sizeAvg = sizeAvg;
} else {
optsOut.size = markerSizeFunc(optsIn && optsIn.size || 10);
optsOut.borderSizes = markerSizeFunc(optsIn.line.width);
}
return optsOut;
}
function convertMarkerSelection(trace, target) {
var optsIn = trace.marker;
var optsOut = {};
if(!target) return optsOut;
if(target.marker && target.marker.symbol) {
optsOut = convertMarkerStyle(Lib.extendFlat({}, optsIn, target.marker));
} else if(target.marker) {
if(target.marker.size) optsOut.size = target.marker.size;
if(target.marker.color) optsOut.colors = target.marker.color;
if(target.marker.opacity !== undefined) optsOut.opacity = target.marker.opacity;
}
return optsOut;
}
function convertTextSelection(gd, trace, target) {
var optsOut = {};
if(!target) return optsOut;
if(target.textfont) {
var optsIn = {
opacity: 1,
text: trace.text,
texttemplate: trace.texttemplate,
textposition: trace.textposition,
textfont: Lib.extendFlat({}, trace.textfont)
};
if(target.textfont) {
Lib.extendFlat(optsIn.textfont, target.textfont);
}
optsOut = convertTextStyle(gd, optsIn);
}
return optsOut;
}
function convertErrorBarStyle(trace, target, plotGlPixelRatio) {
var optsOut = {
capSize: target.width * 2 * plotGlPixelRatio,
lineWidth: target.thickness * plotGlPixelRatio,
color: target.color
};
if(target.copy_ystyle) {
optsOut = trace.error_y;
}
return optsOut;
}
var SYMBOL_SDF_SIZE = constants.SYMBOL_SDF_SIZE;
var SYMBOL_SIZE = constants.SYMBOL_SIZE;
var SYMBOL_STROKE = constants.SYMBOL_STROKE;
var SYMBOL_SDF = {};
var SYMBOL_SVG_CIRCLE = Drawing.symbolFuncs[0](SYMBOL_SIZE * 0.05);
function getSymbolSdf(symbol) {
if(symbol === 'circle') return null;
var symbolPath, symbolSdf;
var symbolNumber = Drawing.symbolNumber(symbol);
var symbolFunc = Drawing.symbolFuncs[symbolNumber % 100];
var symbolNoDot = !!Drawing.symbolNoDot[symbolNumber % 100];
var symbolNoFill = !!Drawing.symbolNoFill[symbolNumber % 100];
var isDot = helpers.isDotSymbol(symbol);
// get symbol sdf from cache or generate it
if(SYMBOL_SDF[symbol]) return SYMBOL_SDF[symbol];
if(isDot && !symbolNoDot) {
symbolPath = symbolFunc(SYMBOL_SIZE * 1.1) + SYMBOL_SVG_CIRCLE;
} else {
symbolPath = symbolFunc(SYMBOL_SIZE);
}
symbolSdf = svgSdf(symbolPath, {
w: SYMBOL_SDF_SIZE,
h: SYMBOL_SDF_SIZE,
viewBox: [-SYMBOL_SIZE, -SYMBOL_SIZE, SYMBOL_SIZE, SYMBOL_SIZE],
stroke: symbolNoFill ? SYMBOL_STROKE : -SYMBOL_STROKE
});
SYMBOL_SDF[symbol] = symbolSdf;
return symbolSdf || null;
}
function convertLinePositions(gd, trace, positions) {
var len = positions.length;
var count = len / 2;
var linePositions;
var i;
if(subTypes.hasLines(trace) && count) {
if(trace.line.shape === 'hv') {
linePositions = [];
for(i = 0; i < count - 1; i++) {
if(isNaN(positions[i * 2]) || isNaN(positions[i * 2 + 1])) {
linePositions.push(NaN, NaN, NaN, NaN);
} else {
linePositions.push(positions[i * 2], positions[i * 2 + 1]);
if(!isNaN(positions[i * 2 + 2]) && !isNaN(positions[i * 2 + 3])) {
linePositions.push(positions[i * 2 + 2], positions[i * 2 + 1]);
} else {
linePositions.push(NaN, NaN);
}
}
}
linePositions.push(positions[len - 2], positions[len - 1]);
} else if(trace.line.shape === 'hvh') {
linePositions = [];
for(i = 0; i < count - 1; i++) {
if(isNaN(positions[i * 2]) || isNaN(positions[i * 2 + 1]) || isNaN(positions[i * 2 + 2]) || isNaN(positions[i * 2 + 3])) {
if(!isNaN(positions[i * 2]) && !isNaN(positions[i * 2 + 1])) {
linePositions.push(positions[i * 2], positions[i * 2 + 1]);
} else {
linePositions.push(NaN, NaN);
}
linePositions.push(NaN, NaN);
} else {
var midPtX = (positions[i * 2] + positions[i * 2 + 2]) / 2;
linePositions.push(
positions[i * 2],
positions[i * 2 + 1],
midPtX,
positions[i * 2 + 1],
midPtX,
positions[i * 2 + 3]
);
}
}
linePositions.push(positions[len - 2], positions[len - 1]);
} else if(trace.line.shape === 'vhv') {
linePositions = [];
for(i = 0; i < count - 1; i++) {
if(isNaN(positions[i * 2]) || isNaN(positions[i * 2 + 1]) || isNaN(positions[i * 2 + 2]) || isNaN(positions[i * 2 + 3])) {
if(!isNaN(positions[i * 2]) && !isNaN(positions[i * 2 + 1])) {
linePositions.push(positions[i * 2], positions[i * 2 + 1]);
} else {
linePositions.push(NaN, NaN);
}
linePositions.push(NaN, NaN);
} else {
var midPtY = (positions[i * 2 + 1] + positions[i * 2 + 3]) / 2;
linePositions.push(
positions[i * 2],
positions[i * 2 + 1],
positions[i * 2],
midPtY,
positions[i * 2 + 2],
midPtY
);
}
}
linePositions.push(positions[len - 2], positions[len - 1]);
} else if(trace.line.shape === 'vh') {
linePositions = [];
for(i = 0; i < count - 1; i++) {
if(isNaN(positions[i * 2]) || isNaN(positions[i * 2 + 1])) {
linePositions.push(NaN, NaN, NaN, NaN);
} else {
linePositions.push(positions[i * 2], positions[i * 2 + 1]);
if(!isNaN(positions[i * 2 + 2]) && !isNaN(positions[i * 2 + 3])) {
linePositions.push(positions[i * 2], positions[i * 2 + 3]);
} else {
linePositions.push(NaN, NaN);
}
}
}
linePositions.push(positions[len - 2], positions[len - 1]);
} else {
linePositions = positions;
}
}
// If we have data with gaps, we ought to use rect joins
// FIXME: get rid of this
var hasNaN = false;
for(i = 0; i < linePositions.length; i++) {
if(isNaN(linePositions[i])) {
hasNaN = true;
break;
}
}
var join = (hasNaN || linePositions.length > constants.TOO_MANY_POINTS) ? 'rect' :
subTypes.hasMarkers(trace) ? 'rect' : 'round';
// fill gaps
if(hasNaN && trace.connectgaps) {
var lastX = linePositions[0];
var lastY = linePositions[1];
for(i = 0; i < linePositions.length; i += 2) {
if(isNaN(linePositions[i]) || isNaN(linePositions[i + 1])) {
linePositions[i] = lastX;
linePositions[i + 1] = lastY;
} else {
lastX = linePositions[i];
lastY = linePositions[i + 1];
}
}
}
return {
join: join,
positions: linePositions
};
}
function convertErrorBarPositions(gd, trace, positions, x, y) {
var makeComputeError = Registry.getComponentMethod('errorbars', 'makeComputeError');
var xa = AxisIDs.getFromId(gd, trace.xaxis);
var ya = AxisIDs.getFromId(gd, trace.yaxis);
var count = positions.length / 2;
var out = {};
function convertOneAxis(coords, ax) {
var axLetter = ax._id.charAt(0);
var opts = trace['error_' + axLetter];
if(opts && opts.visible && (ax.type === 'linear' || ax.type === 'log')) {
var computeError = makeComputeError(opts);
var pOffset = {x: 0, y: 1}[axLetter];
var eOffset = {x: [0, 1, 2, 3], y: [2, 3, 0, 1]}[axLetter];
var errors = new Float64Array(4 * count);
var minShoe = Infinity;
var maxHat = -Infinity;
for(var i = 0, j = 0; i < count; i++, j += 4) {
var dc = coords[i];
if(isNumeric(dc)) {
var dl = positions[i * 2 + pOffset];
var vals = computeError(dc, i);
var lv = vals[0];
var hv = vals[1];
if(isNumeric(lv) && isNumeric(hv)) {
var shoe = dc - lv;
var hat = dc + hv;
errors[j + eOffset[0]] = dl - ax.c2l(shoe);
errors[j + eOffset[1]] = ax.c2l(hat) - dl;
errors[j + eOffset[2]] = 0;
errors[j + eOffset[3]] = 0;
minShoe = Math.min(minShoe, dc - lv);
maxHat = Math.max(maxHat, dc + hv);
}
}
}
out[axLetter] = {
positions: positions,
errors: errors,
_bnds: [minShoe, maxHat]
};
}
}
convertOneAxis(x, xa);
convertOneAxis(y, ya);
return out;
}
function convertTextPosition(gd, trace, textOpts, markerOpts) {
var count = trace._length;
var out = {};
var i;
// corresponds to textPointPosition from component.drawing
if(subTypes.hasMarkers(trace)) {
var fontOpts = textOpts.font;
var align = textOpts.align;
var baseline = textOpts.baseline;
out.offset = new Array(count);
for(i = 0; i < count; i++) {
var ms = markerOpts.sizes ? markerOpts.sizes[i] : markerOpts.size;
var fs = Array.isArray(fontOpts) ? fontOpts[i].size : fontOpts.size;
var a = Array.isArray(align) ?
(align.length > 1 ? align[i] : align[0]) :
align;
var b = Array.isArray(baseline) ?
(baseline.length > 1 ? baseline[i] : baseline[0]) :
baseline;
var hSign = TEXTOFFSETSIGN[a];
var vSign = TEXTOFFSETSIGN[b];
var xPad = ms ? ms / 0.8 + 1 : 0;
var yPad = -vSign * xPad - vSign * 0.5;
out.offset[i] = [hSign * xPad / fs, yPad / fs];
}
}
return out;
}
module.exports = {
style: convertStyle,
markerStyle: convertMarkerStyle,
markerSelection: convertMarkerSelection,
linePositions: convertLinePositions,
errorBarPositions: convertErrorBarPositions,
textPosition: convertTextPosition
};
},{"../../components/drawing":388,"../../components/fx/helpers":402,"../../constants/interactions":478,"../../lib":503,"../../lib/gl_format_color":499,"../../plots/cartesian/axis_ids":558,"../../registry":638,"../scatter/make_bubble_size_func":942,"../scatter/subtypes":950,"./constants":979,"./helpers":984,"color-normalize":89,"fast-isnumeric":190,"svg-path-sdf":310}],981:[function(_dereq_,module,exports){
'use strict';
var Lib = _dereq_('../../lib');
var Registry = _dereq_('../../registry');
var helpers = _dereq_('./helpers');
var attributes = _dereq_('./attributes');
var constants = _dereq_('../scatter/constants');
var subTypes = _dereq_('../scatter/subtypes');
var handleXYDefaults = _dereq_('../scatter/xy_defaults');
var handlePeriodDefaults = _dereq_('../scatter/period_defaults');
var handleMarkerDefaults = _dereq_('../scatter/marker_defaults');
var handleLineDefaults = _dereq_('../scatter/line_defaults');
var handleFillColorDefaults = _dereq_('../scatter/fillcolor_defaults');
var handleTextDefaults = _dereq_('../scatter/text_defaults');
module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout) {
function coerce(attr, dflt) {
return Lib.coerce(traceIn, traceOut, attributes, attr, dflt);
}
var isOpen = traceIn.marker ? helpers.isOpenSymbol(traceIn.marker.symbol) : false;
var isBubble = subTypes.isBubble(traceIn);
var len = handleXYDefaults(traceIn, traceOut, layout, coerce);
if(!len) {
traceOut.visible = false;
return;
}
handlePeriodDefaults(traceIn, traceOut, layout, coerce);
coerce('xhoverformat');
coerce('yhoverformat');
var defaultMode = len < constants.PTS_LINESONLY ? 'lines+markers' : 'lines';
coerce('text');
coerce('hovertext');
coerce('hovertemplate');
coerce('mode', defaultMode);
if(subTypes.hasLines(traceOut)) {
coerce('connectgaps');
handleLineDefaults(traceIn, traceOut, defaultColor, layout, coerce);
coerce('line.shape');
}
if(subTypes.hasMarkers(traceOut)) {
handleMarkerDefaults(traceIn, traceOut, defaultColor, layout, coerce);
coerce('marker.line.width', isOpen || isBubble ? 1 : 0);
}
if(subTypes.hasText(traceOut)) {
coerce('texttemplate');
handleTextDefaults(traceIn, traceOut, layout, coerce);
}
var lineColor = (traceOut.line || {}).color;
var markerColor = (traceOut.marker || {}).color;
coerce('fill');
if(traceOut.fill !== 'none') {
handleFillColorDefaults(traceIn, traceOut, defaultColor, coerce);
}
var errorBarsSupplyDefaults = Registry.getComponentMethod('errorbars', 'supplyDefaults');
errorBarsSupplyDefaults(traceIn, traceOut, lineColor || markerColor || defaultColor, {axis: 'y'});
errorBarsSupplyDefaults(traceIn, traceOut, lineColor || markerColor || defaultColor, {axis: 'x', inherit: 'y'});
Lib.coerceSelectionMarkerOpacity(traceOut, coerce);
};
},{"../../lib":503,"../../registry":638,"../scatter/constants":929,"../scatter/fillcolor_defaults":933,"../scatter/line_defaults":938,"../scatter/marker_defaults":944,"../scatter/period_defaults":945,"../scatter/subtypes":950,"../scatter/text_defaults":951,"../scatter/xy_defaults":952,"./attributes":977,"./helpers":984}],982:[function(_dereq_,module,exports){
'use strict';
var Lib = _dereq_('../../lib');
var Color = _dereq_('../../components/color');
var DESELECTDIM = _dereq_('../../constants/interactions').DESELECTDIM;
function styleTextSelection(cd) {
var cd0 = cd[0];
var trace = cd0.trace;
var stash = cd0.t;
var scene = stash._scene;
var index = stash.index;
var els = scene.selectBatch[index];
var unels = scene.unselectBatch[index];
var baseOpts = scene.textOptions[index];
var selOpts = scene.textSelectedOptions[index] || {};
var unselOpts = scene.textUnselectedOptions[index] || {};
var opts = Lib.extendFlat({}, baseOpts);
var i, j;
if(els.length || unels.length) {
var stc = selOpts.color;
var utc = unselOpts.color;
var base = baseOpts.color;
var hasArrayBase = Array.isArray(base);
opts.color = new Array(trace._length);
for(i = 0; i < els.length; i++) {
j = els[i];
opts.color[j] = stc || (hasArrayBase ? base[j] : base);
}
for(i = 0; i < unels.length; i++) {
j = unels[i];
var basej = hasArrayBase ? base[j] : base;
opts.color[j] = utc ? utc :
stc ? basej : Color.addOpacity(basej, DESELECTDIM);
}
}
scene.glText[index].update(opts);
}
module.exports = {
styleTextSelection: styleTextSelection
};
},{"../../components/color":366,"../../constants/interactions":478,"../../lib":503}],983:[function(_dereq_,module,exports){
'use strict';
var scatterFormatLabels = _dereq_('../scatter/format_labels');
module.exports = function formatLabels(cdi, trace, fullLayout) {
var i = cdi.i;
if(!('x' in cdi)) cdi.x = trace._x[i];
if(!('y' in cdi)) cdi.y = trace._y[i];
return scatterFormatLabels(cdi, trace, fullLayout);
};
},{"../scatter/format_labels":934}],984:[function(_dereq_,module,exports){
'use strict';
var constants = _dereq_('./constants');
exports.isOpenSymbol = function(symbol) {
return (typeof symbol === 'string') ?
constants.OPEN_RE.test(symbol) :
symbol % 200 > 100;
};
exports.isDotSymbol = function(symbol) {
return (typeof symbol === 'string') ?
constants.DOT_RE.test(symbol) :
symbol > 200;
};
},{"./constants":979}],985:[function(_dereq_,module,exports){
'use strict';
var Registry = _dereq_('../../registry');
var Lib = _dereq_('../../lib');
var getTraceColor = _dereq_('../scatter/get_trace_color');
function hoverPoints(pointData, xval, yval, hovermode) {
var cd = pointData.cd;
var stash = cd[0].t;
var trace = cd[0].trace;
var xa = pointData.xa;
var ya = pointData.ya;
var x = stash.x;
var y = stash.y;
var xpx = xa.c2p(xval);
var ypx = ya.c2p(yval);
var maxDistance = pointData.distance;
var ids;
// FIXME: make sure this is a proper way to calc search radius
if(stash.tree) {
var xl = xa.p2c(xpx - maxDistance);
var xr = xa.p2c(xpx + maxDistance);
var yl = ya.p2c(ypx - maxDistance);
var yr = ya.p2c(ypx + maxDistance);
if(hovermode === 'x') {
ids = stash.tree.range(
Math.min(xl, xr), Math.min(ya._rl[0], ya._rl[1]),
Math.max(xl, xr), Math.max(ya._rl[0], ya._rl[1])
);
} else {
ids = stash.tree.range(
Math.min(xl, xr), Math.min(yl, yr),
Math.max(xl, xr), Math.max(yl, yr)
);
}
} else {
ids = stash.ids;
}
// pick the id closest to the point
// note that point possibly may not be found
var k, closestId, ptx, pty, i, dx, dy, dist, dxy;
var minDist = maxDistance;
if(hovermode === 'x') {
var xPeriod = !!trace.xperiodalignment;
var yPeriod = !!trace.yperiodalignment;
for(i = 0; i < ids.length; i++) {
k = ids[i];
ptx = x[k];
dx = Math.abs(xa.c2p(ptx) - xpx);
if(xPeriod) {
var x0 = xa.c2p(trace._xStarts[k]);
var x1 = xa.c2p(trace._xEnds[k]);
dx = (
xpx >= Math.min(x0, x1) &&
xpx <= Math.max(x0, x1)
) ? 0 : Infinity;
}
if(dx < minDist) {
minDist = dx;
pty = y[k];
dy = ya.c2p(pty) - ypx;
if(yPeriod) {
var y0 = ya.c2p(trace._yStarts[k]);
var y1 = ya.c2p(trace._yEnds[k]);
dy = (
ypx >= Math.min(y0, y1) &&
ypx <= Math.max(y0, y1)
) ? 0 : Infinity;
}
dxy = Math.sqrt(dx * dx + dy * dy);
closestId = ids[i];
}
}
} else {
for(i = ids.length - 1; i > -1; i--) {
k = ids[i];
ptx = x[k];
pty = y[k];
dx = xa.c2p(ptx) - xpx;
dy = ya.c2p(pty) - ypx;
dist = Math.sqrt(dx * dx + dy * dy);
if(dist < minDist) {
minDist = dxy = dist;
closestId = k;
}
}
}
pointData.index = closestId;
pointData.distance = minDist;
pointData.dxy = dxy;
if(closestId === undefined) return [pointData];
return [calcHover(pointData, x, y, trace)];
}
function calcHover(pointData, x, y, trace) {
var xa = pointData.xa;
var ya = pointData.ya;
var minDist = pointData.distance;
var dxy = pointData.dxy;
var id = pointData.index;
// the closest data point
var di = {
pointNumber: id,
x: x[id],
y: y[id]
};
// that is single-item arrays_to_calcdata excerpt, since we are doing it for a single point and we don't have to do it beforehead for 1e6 points
di.tx = Array.isArray(trace.text) ? trace.text[id] : trace.text;
di.htx = Array.isArray(trace.hovertext) ? trace.hovertext[id] : trace.hovertext;
di.data = Array.isArray(trace.customdata) ? trace.customdata[id] : trace.customdata;
di.tp = Array.isArray(trace.textposition) ? trace.textposition[id] : trace.textposition;
var font = trace.textfont;
if(font) {
di.ts = Lib.isArrayOrTypedArray(font.size) ? font.size[id] : font.size;
di.tc = Array.isArray(font.color) ? font.color[id] : font.color;
di.tf = Array.isArray(font.family) ? font.family[id] : font.family;
}
var marker = trace.marker;
if(marker) {
di.ms = Lib.isArrayOrTypedArray(marker.size) ? marker.size[id] : marker.size;
di.mo = Lib.isArrayOrTypedArray(marker.opacity) ? marker.opacity[id] : marker.opacity;
di.mx = Lib.isArrayOrTypedArray(marker.symbol) ? marker.symbol[id] : marker.symbol;
di.mc = Lib.isArrayOrTypedArray(marker.color) ? marker.color[id] : marker.color;
}
var line = marker && marker.line;
if(line) {
di.mlc = Array.isArray(line.color) ? line.color[id] : line.color;
di.mlw = Lib.isArrayOrTypedArray(line.width) ? line.width[id] : line.width;
}
var grad = marker && marker.gradient;
if(grad && grad.type !== 'none') {
di.mgt = Array.isArray(grad.type) ? grad.type[id] : grad.type;
di.mgc = Array.isArray(grad.color) ? grad.color[id] : grad.color;
}
var xp = xa.c2p(di.x, true);
var yp = ya.c2p(di.y, true);
var rad = di.mrc || 1;
var hoverlabel = trace.hoverlabel;
if(hoverlabel) {
di.hbg = Array.isArray(hoverlabel.bgcolor) ? hoverlabel.bgcolor[id] : hoverlabel.bgcolor;
di.hbc = Array.isArray(hoverlabel.bordercolor) ? hoverlabel.bordercolor[id] : hoverlabel.bordercolor;
di.hts = Lib.isArrayOrTypedArray(hoverlabel.font.size) ? hoverlabel.font.size[id] : hoverlabel.font.size;
di.htc = Array.isArray(hoverlabel.font.color) ? hoverlabel.font.color[id] : hoverlabel.font.color;
di.htf = Array.isArray(hoverlabel.font.family) ? hoverlabel.font.family[id] : hoverlabel.font.family;
di.hnl = Lib.isArrayOrTypedArray(hoverlabel.namelength) ? hoverlabel.namelength[id] : hoverlabel.namelength;
}
var hoverinfo = trace.hoverinfo;
if(hoverinfo) {
di.hi = Array.isArray(hoverinfo) ? hoverinfo[id] : hoverinfo;
}
var hovertemplate = trace.hovertemplate;
if(hovertemplate) {
di.ht = Array.isArray(hovertemplate) ? hovertemplate[id] : hovertemplate;
}
var fakeCd = {};
fakeCd[pointData.index] = di;
var origX = trace._origX;
var origY = trace._origY;
var pointData2 = Lib.extendFlat({}, pointData, {
color: getTraceColor(trace, di),
x0: xp - rad,
x1: xp + rad,
xLabelVal: origX ? origX[id] : di.x,
y0: yp - rad,
y1: yp + rad,
yLabelVal: origY ? origY[id] : di.y,
cd: fakeCd,
distance: minDist,
spikeDistance: dxy,
hovertemplate: di.ht
});
if(di.htx) pointData2.text = di.htx;
else if(di.tx) pointData2.text = di.tx;
else if(trace.text) pointData2.text = trace.text;
Lib.fillText(di, trace, pointData2);
Registry.getComponentMethod('errorbars', 'hoverInfo')(di, trace, pointData2);
return pointData2;
}
module.exports = {
hoverPoints: hoverPoints,
calcHover: calcHover
};
},{"../../lib":503,"../../registry":638,"../scatter/get_trace_color":935}],986:[function(_dereq_,module,exports){
'use strict';
var hover = _dereq_('./hover');
module.exports = {
moduleType: 'trace',
name: 'scattergl',
basePlotModule: _dereq_('../../plots/cartesian'),
categories: ['gl', 'regl', 'cartesian', 'symbols', 'errorBarsOK', 'showLegend', 'scatter-like'],
attributes: _dereq_('./attributes'),
supplyDefaults: _dereq_('./defaults'),
crossTraceDefaults: _dereq_('../scatter/cross_trace_defaults'),
colorbar: _dereq_('../scatter/marker_colorbar'),
formatLabels: _dereq_('./format_labels'),
calc: _dereq_('./calc'),
plot: _dereq_('./plot'),
hoverPoints: hover.hoverPoints,
selectPoints: _dereq_('./select'),
meta: {
}
};
},{"../../plots/cartesian":568,"../scatter/cross_trace_defaults":931,"../scatter/marker_colorbar":943,"./attributes":977,"./calc":978,"./defaults":981,"./format_labels":983,"./hover":985,"./plot":987,"./select":989}],987:[function(_dereq_,module,exports){
'use strict';
var createScatter = _dereq_('regl-scatter2d');
var createLine = _dereq_('regl-line2d');
var createError = _dereq_('regl-error2d');
var Text = _dereq_('gl-text');
var Lib = _dereq_('../../lib');
var selectMode = _dereq_('../../components/dragelement/helpers').selectMode;
var prepareRegl = _dereq_('../../lib/prepare_regl');
var subTypes = _dereq_('../scatter/subtypes');
var linkTraces = _dereq_('../scatter/link_traces');
var styleTextSelection = _dereq_('./edit_style').styleTextSelection;
function getViewport(fullLayout, xaxis, yaxis, plotGlPixelRatio) {
var gs = fullLayout._size;
var width = fullLayout.width * plotGlPixelRatio;
var height = fullLayout.height * plotGlPixelRatio;
var l = gs.l * plotGlPixelRatio;
var b = gs.b * plotGlPixelRatio;
var r = gs.r * plotGlPixelRatio;
var t = gs.t * plotGlPixelRatio;
var w = gs.w * plotGlPixelRatio;
var h = gs.h * plotGlPixelRatio;
return [
l + xaxis.domain[0] * w,
b + yaxis.domain[0] * h,
(width - r) - (1 - xaxis.domain[1]) * w,
(height - t) - (1 - yaxis.domain[1]) * h
];
}
module.exports = function plot(gd, subplot, cdata) {
if(!cdata.length) return;
var fullLayout = gd._fullLayout;
var scene = subplot._scene;
var xaxis = subplot.xaxis;
var yaxis = subplot.yaxis;
var i, j;
// we may have more subplots than initialized data due to Axes.getSubplots method
if(!scene) return;
var success = prepareRegl(gd, ['ANGLE_instanced_arrays', 'OES_element_index_uint']);
if(!success) {
scene.init();
return;
}
var count = scene.count;
var regl = fullLayout._glcanvas.data()[0].regl;
// that is needed for fills
linkTraces(gd, subplot, cdata);
if(scene.dirty) {
// make sure scenes are created
if(scene.error2d === true) {
scene.error2d = createError(regl);
}
if(scene.line2d === true) {
scene.line2d = createLine(regl);
}
if(scene.scatter2d === true) {
scene.scatter2d = createScatter(regl);
}
if(scene.fill2d === true) {
scene.fill2d = createLine(regl);
}
if(scene.glText === true) {
scene.glText = new Array(count);
for(i = 0; i < count; i++) {
scene.glText[i] = new Text(regl);
}
}
// update main marker options
if(scene.glText) {
if(count > scene.glText.length) {
// add gl text marker
var textsToAdd = count - scene.glText.length;
for(i = 0; i < textsToAdd; i++) {
scene.glText.push(new Text(regl));
}
} else if(count < scene.glText.length) {
// remove gl text marker
var textsToRemove = scene.glText.length - count;
var removedTexts = scene.glText.splice(count, textsToRemove);
removedTexts.forEach(function(text) { text.destroy(); });
}
for(i = 0; i < count; i++) {
scene.glText[i].update(scene.textOptions[i]);
}
}
if(scene.line2d) {
scene.line2d.update(scene.lineOptions);
scene.lineOptions = scene.lineOptions.map(function(lineOptions) {
if(lineOptions && lineOptions.positions) {
var srcPos = lineOptions.positions;
var firstptdef = 0;
while(firstptdef < srcPos.length && (isNaN(srcPos[firstptdef]) || isNaN(srcPos[firstptdef + 1]))) {
firstptdef += 2;
}
var lastptdef = srcPos.length - 2;
while(lastptdef > firstptdef && (isNaN(srcPos[lastptdef]) || isNaN(srcPos[lastptdef + 1]))) {
lastptdef -= 2;
}
lineOptions.positions = srcPos.slice(firstptdef, lastptdef + 2);
}
return lineOptions;
});
scene.line2d.update(scene.lineOptions);
}
if(scene.error2d) {
var errorBatch = (scene.errorXOptions || []).concat(scene.errorYOptions || []);
scene.error2d.update(errorBatch);
}
if(scene.scatter2d) {
scene.scatter2d.update(scene.markerOptions);
}
// fill requires linked traces, so we generate it's positions here
scene.fillOrder = Lib.repeat(null, count);
if(scene.fill2d) {
scene.fillOptions = scene.fillOptions.map(function(fillOptions, i) {
var cdscatter = cdata[i];
if(!fillOptions || !cdscatter || !cdscatter[0] || !cdscatter[0].trace) return;
var cd = cdscatter[0];
var trace = cd.trace;
var stash = cd.t;
var lineOptions = scene.lineOptions[i];
var last, j;
var fillData = [];
if(trace._ownfill) fillData.push(i);
if(trace._nexttrace) fillData.push(i + 1);
if(fillData.length) scene.fillOrder[i] = fillData;
var pos = [];
var srcPos = (lineOptions && lineOptions.positions) || stash.positions;
var firstptdef, lastptdef;
if(trace.fill === 'tozeroy') {
firstptdef = 0;
while(firstptdef < srcPos.length && isNaN(srcPos[firstptdef + 1])) {
firstptdef += 2;
}
lastptdef = srcPos.length - 2;
while(lastptdef > firstptdef && isNaN(srcPos[lastptdef + 1])) {
lastptdef -= 2;
}
if(srcPos[firstptdef + 1] !== 0) {
pos = [srcPos[firstptdef], 0];
}
pos = pos.concat(srcPos.slice(firstptdef, lastptdef + 2));
if(srcPos[lastptdef + 1] !== 0) {
pos = pos.concat([srcPos[lastptdef], 0]);
}
} else if(trace.fill === 'tozerox') {
firstptdef = 0;
while(firstptdef < srcPos.length && isNaN(srcPos[firstptdef])) {
firstptdef += 2;
}
lastptdef = srcPos.length - 2;
while(lastptdef > firstptdef && isNaN(srcPos[lastptdef])) {
lastptdef -= 2;
}
if(srcPos[firstptdef] !== 0) {
pos = [0, srcPos[firstptdef + 1]];
}
pos = pos.concat(srcPos.slice(firstptdef, lastptdef + 2));
if(srcPos[lastptdef] !== 0) {
pos = pos.concat([ 0, srcPos[lastptdef + 1]]);
}
} else if(trace.fill === 'toself' || trace.fill === 'tonext') {
pos = [];
last = 0;
fillOptions.splitNull = true;
for(j = 0; j < srcPos.length; j += 2) {
if(isNaN(srcPos[j]) || isNaN(srcPos[j + 1])) {
pos = pos.concat(srcPos.slice(last, j));
pos.push(srcPos[last], srcPos[last + 1]);
pos.push(null, null); // keep null to mark end of polygon
last = j + 2;
}
}
pos = pos.concat(srcPos.slice(last));
if(last) {
pos.push(srcPos[last], srcPos[last + 1]);
}
} else {
var nextTrace = trace._nexttrace;
if(nextTrace) {
var nextOptions = scene.lineOptions[i + 1];
if(nextOptions) {
var nextPos = nextOptions.positions;
if(trace.fill === 'tonexty') {
pos = srcPos.slice();
for(i = Math.floor(nextPos.length / 2); i--;) {
var xx = nextPos[i * 2];
var yy = nextPos[i * 2 + 1];
if(isNaN(xx) || isNaN(yy)) continue;
pos.push(xx, yy);
}
fillOptions.fill = nextTrace.fillcolor;
}
}
}
}
// detect prev trace positions to exclude from current fill
if(trace._prevtrace && trace._prevtrace.fill === 'tonext') {
var prevLinePos = scene.lineOptions[i - 1].positions;
// FIXME: likely this logic should be tested better
var offset = pos.length / 2;
last = offset;
var hole = [last];
for(j = 0; j < prevLinePos.length; j += 2) {
if(isNaN(prevLinePos[j]) || isNaN(prevLinePos[j + 1])) {
hole.push(j / 2 + offset + 1);
last = j + 2;
}
}
pos = pos.concat(prevLinePos);
fillOptions.hole = hole;
}
fillOptions.fillmode = trace.fill;
fillOptions.opacity = trace.opacity;
fillOptions.positions = pos;
return fillOptions;
});
scene.fill2d.update(scene.fillOptions);
}
}
// form batch arrays, and check for selected points
var dragmode = fullLayout.dragmode;
var isSelectMode = selectMode(dragmode);
var clickSelectEnabled = fullLayout.clickmode.indexOf('select') > -1;
for(i = 0; i < count; i++) {
var cd0 = cdata[i][0];
var trace = cd0.trace;
var stash = cd0.t;
var index = stash.index;
var len = trace._length;
var x = stash.x;
var y = stash.y;
if(trace.selectedpoints || isSelectMode || clickSelectEnabled) {
if(!isSelectMode) isSelectMode = true;
// regenerate scene batch, if traces number changed during selection
if(trace.selectedpoints) {
var selPts = scene.selectBatch[index] = Lib.selIndices2selPoints(trace);
var selDict = {};
for(j = 0; j < selPts.length; j++) {
selDict[selPts[j]] = 1;
}
var unselPts = [];
for(j = 0; j < len; j++) {
if(!selDict[j]) unselPts.push(j);
}
scene.unselectBatch[index] = unselPts;
}
// precalculate px coords since we are not going to pan during select
// TODO, could do better here e.g.
// - spin that in a webworker
// - compute selection from polygons in data coordinates
// (maybe just for linear axes)
var xpx = stash.xpx = new Array(len);
var ypx = stash.ypx = new Array(len);
for(j = 0; j < len; j++) {
xpx[j] = xaxis.c2p(x[j]);
ypx[j] = yaxis.c2p(y[j]);
}
} else {
stash.xpx = stash.ypx = null;
}
}
if(isSelectMode) {
// create scatter instance by cloning scatter2d
if(!scene.select2d) {
scene.select2d = createScatter(fullLayout._glcanvas.data()[1].regl);
}
// use unselected styles on 'context' canvas
if(scene.scatter2d) {
var unselOpts = new Array(count);
for(i = 0; i < count; i++) {
unselOpts[i] = scene.selectBatch[i].length || scene.unselectBatch[i].length ?
scene.markerUnselectedOptions[i] :
{};
}
scene.scatter2d.update(unselOpts);
}
// use selected style on 'focus' canvas
if(scene.select2d) {
scene.select2d.update(scene.markerOptions);
scene.select2d.update(scene.markerSelectedOptions);
}
if(scene.glText) {
cdata.forEach(function(cdscatter) {
var trace = ((cdscatter || [])[0] || {}).trace || {};
if(subTypes.hasText(trace)) {
styleTextSelection(cdscatter);
}
});
}
} else {
// reset 'context' scatter2d opts to base opts,
// thus unsetting markerUnselectedOptions from selection
if(scene.scatter2d) {
scene.scatter2d.update(scene.markerOptions);
}
}
// provide viewport and range
var vpRange0 = {
viewport: getViewport(fullLayout, xaxis, yaxis, gd._context.plotGlPixelRatio),
// TODO do we need those fallbacks?
range: [
(xaxis._rl || xaxis.range)[0],
(yaxis._rl || yaxis.range)[0],
(xaxis._rl || xaxis.range)[1],
(yaxis._rl || yaxis.range)[1]
]
};
var vpRange = Lib.repeat(vpRange0, scene.count);
// upload viewport/range data to GPU
if(scene.fill2d) {
scene.fill2d.update(vpRange);
}
if(scene.line2d) {
scene.line2d.update(vpRange);
}
if(scene.error2d) {
scene.error2d.update(vpRange.concat(vpRange));
}
if(scene.scatter2d) {
scene.scatter2d.update(vpRange);
}
if(scene.select2d) {
scene.select2d.update(vpRange);
}
if(scene.glText) {
scene.glText.forEach(function(text) { text.update(vpRange0); });
}
};
},{"../../components/dragelement/helpers":384,"../../lib":503,"../../lib/prepare_regl":516,"../scatter/link_traces":941,"../scatter/subtypes":950,"./edit_style":982,"gl-text":225,"regl-error2d":279,"regl-line2d":280,"regl-scatter2d":281}],988:[function(_dereq_,module,exports){
'use strict';
var Lib = _dereq_('../../lib');
// make sure scene exists on subplot, return it
module.exports = function sceneUpdate(gd, subplot) {
var scene = subplot._scene;
var resetOpts = {
// number of traces in subplot, since scene:subplot -> 1:1
count: 0,
// whether scene requires init hook in plot call (dirty plot call)
dirty: true,
// last used options
lineOptions: [],
fillOptions: [],
markerOptions: [],
markerSelectedOptions: [],
markerUnselectedOptions: [],
errorXOptions: [],
errorYOptions: [],
textOptions: [],
textSelectedOptions: [],
textUnselectedOptions: [],
// selection batches
selectBatch: [],
unselectBatch: []
};
// regl- component stubs, initialized in dirty plot call
var initOpts = {
fill2d: false,
scatter2d: false,
error2d: false,
line2d: false,
glText: false,
select2d: false
};
if(!subplot._scene) {
scene = subplot._scene = {};
scene.init = function init() {
Lib.extendFlat(scene, initOpts, resetOpts);
};
scene.init();
// apply new option to all regl components (used on drag)
scene.update = function update(opt) {
var opts = Lib.repeat(opt, scene.count);
if(scene.fill2d) scene.fill2d.update(opts);
if(scene.scatter2d) scene.scatter2d.update(opts);
if(scene.line2d) scene.line2d.update(opts);
if(scene.error2d) scene.error2d.update(opts.concat(opts));
if(scene.select2d) scene.select2d.update(opts);
if(scene.glText) {
for(var i = 0; i < scene.count; i++) {
scene.glText[i].update(opt);
}
}
};
// draw traces in proper order
scene.draw = function draw() {
var count = scene.count;
var fill2d = scene.fill2d;
var error2d = scene.error2d;
var line2d = scene.line2d;
var scatter2d = scene.scatter2d;
var glText = scene.glText;
var select2d = scene.select2d;
var selectBatch = scene.selectBatch;
var unselectBatch = scene.unselectBatch;
for(var i = 0; i < count; i++) {
if(fill2d && scene.fillOrder[i]) {
fill2d.draw(scene.fillOrder[i]);
}
if(line2d && scene.lineOptions[i]) {
line2d.draw(i);
}
if(error2d) {
if(scene.errorXOptions[i]) error2d.draw(i);
if(scene.errorYOptions[i]) error2d.draw(i + count);
}
if(scatter2d && scene.markerOptions[i]) {
if(unselectBatch[i].length) {
var arg = Lib.repeat([], scene.count);
arg[i] = unselectBatch[i];
scatter2d.draw(arg);
} else if(!selectBatch[i].length) {
scatter2d.draw(i);
}
}
if(glText[i] && scene.textOptions[i]) {
glText[i].render();
}
}
if(select2d) {
select2d.draw(selectBatch);
}
scene.dirty = false;
};
// remove scene resources
scene.destroy = function destroy() {
if(scene.fill2d && scene.fill2d.destroy) scene.fill2d.destroy();
if(scene.scatter2d && scene.scatter2d.destroy) scene.scatter2d.destroy();
if(scene.error2d && scene.error2d.destroy) scene.error2d.destroy();
if(scene.line2d && scene.line2d.destroy) scene.line2d.destroy();
if(scene.select2d && scene.select2d.destroy) scene.select2d.destroy();
if(scene.glText) {
scene.glText.forEach(function(text) {
if(text.destroy) text.destroy();
});
}
scene.lineOptions = null;
scene.fillOptions = null;
scene.markerOptions = null;
scene.markerSelectedOptions = null;
scene.markerUnselectedOptions = null;
scene.errorXOptions = null;
scene.errorYOptions = null;
scene.textOptions = null;
scene.textSelectedOptions = null;
scene.textUnselectedOptions = null;
scene.selectBatch = null;
scene.unselectBatch = null;
// we can't just delete _scene, because `destroy` is called in the
// middle of supplyDefaults, before relinkPrivateKeys which will put it back.
subplot._scene = null;
};
}
// in case if we have scene from the last calc - reset data
if(!scene.dirty) {
Lib.extendFlat(scene, resetOpts);
}
return scene;
};
},{"../../lib":503}],989:[function(_dereq_,module,exports){
'use strict';
var subTypes = _dereq_('../scatter/subtypes');
var styleTextSelection = _dereq_('./edit_style').styleTextSelection;
module.exports = function select(searchInfo, selectionTester) {
var cd = searchInfo.cd;
var xa = searchInfo.xaxis;
var ya = searchInfo.yaxis;
var selection = [];
var trace = cd[0].trace;
var stash = cd[0].t;
var len = trace._length;
var x = stash.x;
var y = stash.y;
var scene = stash._scene;
var index = stash.index;
if(!scene) return selection;
var hasText = subTypes.hasText(trace);
var hasMarkers = subTypes.hasMarkers(trace);
var hasOnlyLines = !hasMarkers && !hasText;
if(trace.visible !== true || hasOnlyLines) return selection;
var els = [];
var unels = [];
// degenerate polygon does not enable selection
// filter out points by visible scatter ones
if(selectionTester !== false && !selectionTester.degenerate) {
for(var i = 0; i < len; i++) {
if(selectionTester.contains([stash.xpx[i], stash.ypx[i]], false, i, searchInfo)) {
els.push(i);
selection.push({
pointNumber: i,
x: xa.c2d(x[i]),
y: ya.c2d(y[i])
});
} else {
unels.push(i);
}
}
}
if(hasMarkers) {
var scatter2d = scene.scatter2d;
if(!els.length && !unels.length) {
// reset to base styles when clearing
var baseOpts = new Array(scene.count);
baseOpts[index] = scene.markerOptions[index];
scatter2d.update.apply(scatter2d, baseOpts);
} else if(!scene.selectBatch[index].length && !scene.unselectBatch[index].length) {
// set unselected styles on 'context' canvas (if not done already)
var unselOpts = new Array(scene.count);
unselOpts[index] = scene.markerUnselectedOptions[index];
scatter2d.update.apply(scatter2d, unselOpts);
}
}
scene.selectBatch[index] = els;
scene.unselectBatch[index] = unels;
if(hasText) {
styleTextSelection(cd);
}
return selection;
};
},{"../scatter/subtypes":950,"./edit_style":982}],990:[function(_dereq_,module,exports){
'use strict';
var hovertemplateAttrs = _dereq_('../../plots/template_attributes').hovertemplateAttrs;
var texttemplateAttrs = _dereq_('../../plots/template_attributes').texttemplateAttrs;
var scatterGeoAttrs = _dereq_('../scattergeo/attributes');
var scatterAttrs = _dereq_('../scatter/attributes');
var mapboxAttrs = _dereq_('../../plots/mapbox/layout_attributes');
var baseAttrs = _dereq_('../../plots/attributes');
var colorScaleAttrs = _dereq_('../../components/colorscale/attributes');
var extendFlat = _dereq_('../../lib/extend').extendFlat;
var overrideAll = _dereq_('../../plot_api/edit_types').overrideAll;
var lineAttrs = scatterGeoAttrs.line;
var markerAttrs = scatterGeoAttrs.marker;
module.exports = overrideAll({
lon: scatterGeoAttrs.lon,
lat: scatterGeoAttrs.lat,
// locations
// locationmode
mode: extendFlat({}, scatterAttrs.mode, {
dflt: 'markers',
}),
text: extendFlat({}, scatterAttrs.text, {
}),
texttemplate: texttemplateAttrs({editType: 'plot'}, {
keys: ['lat', 'lon', 'text']
}),
hovertext: extendFlat({}, scatterAttrs.hovertext, {
}),
line: {
color: lineAttrs.color,
width: lineAttrs.width
// TODO
// dash: dash
},
connectgaps: scatterAttrs.connectgaps,
marker: extendFlat({
symbol: {
valType: 'string',
dflt: 'circle',
arrayOk: true,
},
angle: {
valType: 'number',
dflt: 'auto',
arrayOk: true,
},
allowoverlap: {
valType: 'boolean',
dflt: false,
},
opacity: markerAttrs.opacity,
size: markerAttrs.size,
sizeref: markerAttrs.sizeref,
sizemin: markerAttrs.sizemin,
sizemode: markerAttrs.sizemode
},
colorScaleAttrs('marker')
// line
),
fill: scatterGeoAttrs.fill,
fillcolor: scatterAttrs.fillcolor,
textfont: mapboxAttrs.layers.symbol.textfont,
textposition: mapboxAttrs.layers.symbol.textposition,
below: {
valType: 'string',
},
selected: {
marker: scatterAttrs.selected.marker
},
unselected: {
marker: scatterAttrs.unselected.marker
},
hoverinfo: extendFlat({}, baseAttrs.hoverinfo, {
flags: ['lon', 'lat', 'text', 'name']
}),
hovertemplate: hovertemplateAttrs(),
}, 'calc', 'nested');
},{"../../components/colorscale/attributes":373,"../../lib/extend":493,"../../plot_api/edit_types":536,"../../plots/attributes":550,"../../plots/mapbox/layout_attributes":615,"../../plots/template_attributes":633,"../scatter/attributes":925,"../scattergeo/attributes":967}],991:[function(_dereq_,module,exports){
'use strict';
var isNumeric = _dereq_('fast-isnumeric');
var Lib = _dereq_('../../lib');
var BADNUM = _dereq_('../../constants/numerical').BADNUM;
var geoJsonUtils = _dereq_('../../lib/geojson_utils');
var Colorscale = _dereq_('../../components/colorscale');
var Drawing = _dereq_('../../components/drawing');
var makeBubbleSizeFn = _dereq_('../scatter/make_bubble_size_func');
var subTypes = _dereq_('../scatter/subtypes');
var convertTextOpts = _dereq_('../../plots/mapbox/convert_text_opts');
var appendArrayPointValue = _dereq_('../../components/fx/helpers').appendArrayPointValue;
var NEWLINES = _dereq_('../../lib/svg_text_utils').NEWLINES;
var BR_TAG_ALL = _dereq_('../../lib/svg_text_utils').BR_TAG_ALL;
module.exports = function convert(gd, calcTrace) {
var trace = calcTrace[0].trace;
var isVisible = (trace.visible === true && trace._length !== 0);
var hasFill = (trace.fill !== 'none');
var hasLines = subTypes.hasLines(trace);
var hasMarkers = subTypes.hasMarkers(trace);
var hasText = subTypes.hasText(trace);
var hasCircles = (hasMarkers && trace.marker.symbol === 'circle');
var hasSymbols = (hasMarkers && trace.marker.symbol !== 'circle');
var fill = initContainer();
var line = initContainer();
var circle = initContainer();
var symbol = initContainer();
var opts = {
fill: fill,
line: line,
circle: circle,
symbol: symbol
};
// early return if not visible or placeholder
if(!isVisible) return opts;
// fill layer and line layer use the same coords
var lineCoords;
if(hasFill || hasLines) {
lineCoords = geoJsonUtils.calcTraceToLineCoords(calcTrace);
}
if(hasFill) {
fill.geojson = geoJsonUtils.makePolygon(lineCoords);
fill.layout.visibility = 'visible';
Lib.extendFlat(fill.paint, {
'fill-color': trace.fillcolor
});
}
if(hasLines) {
line.geojson = geoJsonUtils.makeLine(lineCoords);
line.layout.visibility = 'visible';
Lib.extendFlat(line.paint, {
'line-width': trace.line.width,
'line-color': trace.line.color,
'line-opacity': trace.opacity
});
// TODO convert line.dash into line-dasharray
}
if(hasCircles) {
var circleOpts = makeCircleOpts(calcTrace);
circle.geojson = circleOpts.geojson;
circle.layout.visibility = 'visible';
Lib.extendFlat(circle.paint, {
'circle-color': circleOpts.mcc,
'circle-radius': circleOpts.mrc,
'circle-opacity': circleOpts.mo
});
}
if(hasSymbols || hasText) {
symbol.geojson = makeSymbolGeoJSON(calcTrace, gd);
Lib.extendFlat(symbol.layout, {
visibility: 'visible',
'icon-image': '{symbol}-15',
'text-field': '{text}'
});
if(hasSymbols) {
Lib.extendFlat(symbol.layout, {
'icon-size': trace.marker.size / 10
});
if('angle' in trace.marker && trace.marker.angle !== 'auto') {
Lib.extendFlat(symbol.layout, {
// unfortunately cant use {angle} do to this issue:
// https://github.com/mapbox/mapbox-gl-js/issues/873
'icon-rotate': {
type: 'identity', property: 'angle'
},
'icon-rotation-alignment': 'map'
});
}
symbol.layout['icon-allow-overlap'] = trace.marker.allowoverlap;
Lib.extendFlat(symbol.paint, {
'icon-opacity': trace.opacity * trace.marker.opacity,
// TODO does not work ??
'icon-color': trace.marker.color
});
}
if(hasText) {
var iconSize = (trace.marker || {}).size;
var textOpts = convertTextOpts(trace.textposition, iconSize);
// all data-driven below !!
Lib.extendFlat(symbol.layout, {
'text-size': trace.textfont.size,
'text-anchor': textOpts.anchor,
'text-offset': textOpts.offset
// TODO font family
// 'text-font': symbol.textfont.family.split(', '),
});
Lib.extendFlat(symbol.paint, {
'text-color': trace.textfont.color,
'text-opacity': trace.opacity
});
}
}
return opts;
};
function initContainer() {
return {
geojson: geoJsonUtils.makeBlank(),
layout: { visibility: 'none' },
paint: {}
};
}
function makeCircleOpts(calcTrace) {
var trace = calcTrace[0].trace;
var marker = trace.marker;
var selectedpoints = trace.selectedpoints;
var arrayColor = Lib.isArrayOrTypedArray(marker.color);
var arraySize = Lib.isArrayOrTypedArray(marker.size);
var arrayOpacity = Lib.isArrayOrTypedArray(marker.opacity);
var i;
function addTraceOpacity(o) { return trace.opacity * o; }
function size2radius(s) { return s / 2; }
var colorFn;
if(arrayColor) {
if(Colorscale.hasColorscale(trace, 'marker')) {
colorFn = Colorscale.makeColorScaleFuncFromTrace(marker);
} else {
colorFn = Lib.identity;
}
}
var sizeFn;
if(arraySize) {
sizeFn = makeBubbleSizeFn(trace);
}
var opacityFn;
if(arrayOpacity) {
opacityFn = function(mo) {
var mo2 = isNumeric(mo) ? +Lib.constrain(mo, 0, 1) : 0;
return addTraceOpacity(mo2);
};
}
var features = [];
for(i = 0; i < calcTrace.length; i++) {
var calcPt = calcTrace[i];
var lonlat = calcPt.lonlat;
if(isBADNUM(lonlat)) continue;
var props = {};
if(colorFn) props.mcc = calcPt.mcc = colorFn(calcPt.mc);
if(sizeFn) props.mrc = calcPt.mrc = sizeFn(calcPt.ms);
if(opacityFn) props.mo = opacityFn(calcPt.mo);
if(selectedpoints) props.selected = calcPt.selected || 0;
features.push({
type: 'Feature',
geometry: {type: 'Point', coordinates: lonlat},
properties: props
});
}
var fns;
if(selectedpoints) {
fns = Drawing.makeSelectedPointStyleFns(trace);
for(i = 0; i < features.length; i++) {
var d = features[i].properties;
if(fns.selectedOpacityFn) {
d.mo = addTraceOpacity(fns.selectedOpacityFn(d));
}
if(fns.selectedColorFn) {
d.mcc = fns.selectedColorFn(d);
}
if(fns.selectedSizeFn) {
d.mrc = fns.selectedSizeFn(d);
}
}
}
return {
geojson: {type: 'FeatureCollection', features: features},
mcc: arrayColor || (fns && fns.selectedColorFn) ?
{type: 'identity', property: 'mcc'} :
marker.color,
mrc: arraySize || (fns && fns.selectedSizeFn) ?
{type: 'identity', property: 'mrc'} :
size2radius(marker.size),
mo: arrayOpacity || (fns && fns.selectedOpacityFn) ?
{type: 'identity', property: 'mo'} :
addTraceOpacity(marker.opacity)
};
}
function makeSymbolGeoJSON(calcTrace, gd) {
var fullLayout = gd._fullLayout;
var trace = calcTrace[0].trace;
var marker = trace.marker || {};
var symbol = marker.symbol;
var angle = marker.angle;
var fillSymbol = (symbol !== 'circle') ?
getFillFunc(symbol) :
blankFillFunc;
var fillAngle = (angle !== 'auto') ?
getFillFunc(angle, true) :
blankFillFunc;
var fillText = subTypes.hasText(trace) ?
getFillFunc(trace.text) :
blankFillFunc;
var features = [];
for(var i = 0; i < calcTrace.length; i++) {
var calcPt = calcTrace[i];
if(isBADNUM(calcPt.lonlat)) continue;
var texttemplate = trace.texttemplate;
var text;
if(texttemplate) {
var tt = Array.isArray(texttemplate) ? (texttemplate[i] || '') : texttemplate;
var labels = trace._module.formatLabels(calcPt, trace, fullLayout);
var pointValues = {};
appendArrayPointValue(pointValues, trace, calcPt.i);
var meta = trace._meta || {};
text = Lib.texttemplateString(tt, labels, fullLayout._d3locale, pointValues, calcPt, meta);
} else {
text = fillText(i);
}
if(text) {
text = text.replace(NEWLINES, '').replace(BR_TAG_ALL, '\n');
}
features.push({
type: 'Feature',
geometry: {
type: 'Point',
coordinates: calcPt.lonlat
},
properties: {
symbol: fillSymbol(i),
angle: fillAngle(i),
text: text
}
});
}
return {
type: 'FeatureCollection',
features: features
};
}
function getFillFunc(attr, numeric) {
if(Lib.isArrayOrTypedArray(attr)) {
if(numeric) {
return function(i) { return isNumeric(attr[i]) ? +attr[i] : 0; };
}
return function(i) { return attr[i]; };
} else if(attr) {
return function() { return attr; };
} else {
return blankFillFunc;
}
}
function blankFillFunc() { return ''; }
// only need to check lon (OR lat)
function isBADNUM(lonlat) {
return lonlat[0] === BADNUM;
}
},{"../../components/colorscale":378,"../../components/drawing":388,"../../components/fx/helpers":402,"../../constants/numerical":479,"../../lib":503,"../../lib/geojson_utils":497,"../../lib/svg_text_utils":529,"../../plots/mapbox/convert_text_opts":612,"../scatter/make_bubble_size_func":942,"../scatter/subtypes":950,"fast-isnumeric":190}],992:[function(_dereq_,module,exports){
'use strict';
var Lib = _dereq_('../../lib');
var subTypes = _dereq_('../scatter/subtypes');
var handleMarkerDefaults = _dereq_('../scatter/marker_defaults');
var handleLineDefaults = _dereq_('../scatter/line_defaults');
var handleTextDefaults = _dereq_('../scatter/text_defaults');
var handleFillColorDefaults = _dereq_('../scatter/fillcolor_defaults');
var attributes = _dereq_('./attributes');
module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout) {
function coerce(attr, dflt) {
return Lib.coerce(traceIn, traceOut, attributes, attr, dflt);
}
var len = handleLonLatDefaults(traceIn, traceOut, coerce);
if(!len) {
traceOut.visible = false;
return;
}
coerce('text');
coerce('texttemplate');
coerce('hovertext');
coerce('hovertemplate');
coerce('mode');
coerce('below');
if(subTypes.hasLines(traceOut)) {
handleLineDefaults(traceIn, traceOut, defaultColor, layout, coerce, {noDash: true});
coerce('connectgaps');
}
if(subTypes.hasMarkers(traceOut)) {
handleMarkerDefaults(traceIn, traceOut, defaultColor, layout, coerce, {noLine: true});
coerce('marker.allowoverlap');
coerce('marker.angle');
// array marker.size and marker.color are only supported with circles
var marker = traceOut.marker;
if(marker.symbol !== 'circle') {
if(Lib.isArrayOrTypedArray(marker.size)) marker.size = marker.size[0];
if(Lib.isArrayOrTypedArray(marker.color)) marker.color = marker.color[0];
}
}
if(subTypes.hasText(traceOut)) {
handleTextDefaults(traceIn, traceOut, layout, coerce, {noSelect: true});
}
coerce('fill');
if(traceOut.fill !== 'none') {
handleFillColorDefaults(traceIn, traceOut, defaultColor, coerce);
}
Lib.coerceSelectionMarkerOpacity(traceOut, coerce);
};
function handleLonLatDefaults(traceIn, traceOut, coerce) {
var lon = coerce('lon') || [];
var lat = coerce('lat') || [];
var len = Math.min(lon.length, lat.length);
traceOut._length = len;
return len;
}
},{"../../lib":503,"../scatter/fillcolor_defaults":933,"../scatter/line_defaults":938,"../scatter/marker_defaults":944,"../scatter/subtypes":950,"../scatter/text_defaults":951,"./attributes":990}],993:[function(_dereq_,module,exports){
'use strict';
module.exports = function eventData(out, pt) {
out.lon = pt.lon;
out.lat = pt.lat;
return out;
};
},{}],994:[function(_dereq_,module,exports){
'use strict';
var Axes = _dereq_('../../plots/cartesian/axes');
module.exports = function formatLabels(cdi, trace, fullLayout) {
var labels = {};
var subplot = fullLayout[trace.subplot]._subplot;
var ax = subplot.mockAxis;
var lonlat = cdi.lonlat;
labels.lonLabel = Axes.tickText(ax, ax.c2l(lonlat[0]), true).text;
labels.latLabel = Axes.tickText(ax, ax.c2l(lonlat[1]), true).text;
return labels;
};
},{"../../plots/cartesian/axes":554}],995:[function(_dereq_,module,exports){
'use strict';
var Fx = _dereq_('../../components/fx');
var Lib = _dereq_('../../lib');
var getTraceColor = _dereq_('../scatter/get_trace_color');
var fillText = Lib.fillText;
var BADNUM = _dereq_('../../constants/numerical').BADNUM;
function hoverPoints(pointData, xval, yval) {
var cd = pointData.cd;
var trace = cd[0].trace;
var xa = pointData.xa;
var ya = pointData.ya;
var subplot = pointData.subplot;
// compute winding number about [-180, 180] globe
var winding = (xval >= 0) ?
Math.floor((xval + 180) / 360) :
Math.ceil((xval - 180) / 360);
// shift longitude to [-180, 180] to determine closest point
var lonShift = winding * 360;
var xval2 = xval - lonShift;
function distFn(d) {
var lonlat = d.lonlat;
if(lonlat[0] === BADNUM) return Infinity;
var lon = Lib.modHalf(lonlat[0], 360);
var lat = lonlat[1];
var pt = subplot.project([lon, lat]);
var dx = pt.x - xa.c2p([xval2, lat]);
var dy = pt.y - ya.c2p([lon, yval]);
var rad = Math.max(3, d.mrc || 0);
return Math.max(Math.sqrt(dx * dx + dy * dy) - rad, 1 - 3 / rad);
}
Fx.getClosest(cd, distFn, pointData);
// skip the rest (for this trace) if we didn't find a close point
if(pointData.index === false) return;
var di = cd[pointData.index];
var lonlat = di.lonlat;
var lonlatShifted = [Lib.modHalf(lonlat[0], 360) + lonShift, lonlat[1]];
// shift labels back to original winded globe
var xc = xa.c2p(lonlatShifted);
var yc = ya.c2p(lonlatShifted);
var rad = di.mrc || 1;
pointData.x0 = xc - rad;
pointData.x1 = xc + rad;
pointData.y0 = yc - rad;
pointData.y1 = yc + rad;
var fullLayout = {};
fullLayout[trace.subplot] = {_subplot: subplot};
var labels = trace._module.formatLabels(di, trace, fullLayout);
pointData.lonLabel = labels.lonLabel;
pointData.latLabel = labels.latLabel;
pointData.color = getTraceColor(trace, di);
pointData.extraText = getExtraText(trace, di, cd[0].t.labels);
pointData.hovertemplate = trace.hovertemplate;
return [pointData];
}
function getExtraText(trace, di, labels) {
if(trace.hovertemplate) return;
var hoverinfo = di.hi || trace.hoverinfo;
var parts = hoverinfo.split('+');
var isAll = parts.indexOf('all') !== -1;
var hasLon = parts.indexOf('lon') !== -1;
var hasLat = parts.indexOf('lat') !== -1;
var lonlat = di.lonlat;
var text = [];
// TODO should we use a mock axis to format hover?
// If so, we'll need to make precision be zoom-level dependent
function format(v) {
return v + '\u00B0';
}
if(isAll || (hasLon && hasLat)) {
text.push('(' + format(lonlat[1]) + ', ' + format(lonlat[0]) + ')');
} else if(hasLon) {
text.push(labels.lon + format(lonlat[0]));
} else if(hasLat) {
text.push(labels.lat + format(lonlat[1]));
}
if(isAll || parts.indexOf('text') !== -1) {
fillText(di, trace, text);
}
return text.join('
');
}
module.exports = {
hoverPoints: hoverPoints,
getExtraText: getExtraText
};
},{"../../components/fx":406,"../../constants/numerical":479,"../../lib":503,"../scatter/get_trace_color":935}],996:[function(_dereq_,module,exports){
'use strict';
module.exports = {
attributes: _dereq_('./attributes'),
supplyDefaults: _dereq_('./defaults'),
colorbar: _dereq_('../scatter/marker_colorbar'),
formatLabels: _dereq_('./format_labels'),
calc: _dereq_('../scattergeo/calc'),
plot: _dereq_('./plot'),
hoverPoints: _dereq_('./hover').hoverPoints,
eventData: _dereq_('./event_data'),
selectPoints: _dereq_('./select'),
styleOnSelect: function(_, cd) {
if(cd) {
var trace = cd[0].trace;
trace._glTrace.update(cd);
}
},
moduleType: 'trace',
name: 'scattermapbox',
basePlotModule: _dereq_('../../plots/mapbox'),
categories: ['mapbox', 'gl', 'symbols', 'showLegend', 'scatter-like'],
meta: {
}
};
},{"../../plots/mapbox":613,"../scatter/marker_colorbar":943,"../scattergeo/calc":968,"./attributes":990,"./defaults":992,"./event_data":993,"./format_labels":994,"./hover":995,"./plot":997,"./select":998}],997:[function(_dereq_,module,exports){
'use strict';
var convert = _dereq_('./convert');
var LAYER_PREFIX = _dereq_('../../plots/mapbox/constants').traceLayerPrefix;
var ORDER = ['fill', 'line', 'circle', 'symbol'];
function ScatterMapbox(subplot, uid) {
this.type = 'scattermapbox';
this.subplot = subplot;
this.uid = uid;
this.sourceIds = {
fill: 'source-' + uid + '-fill',
line: 'source-' + uid + '-line',
circle: 'source-' + uid + '-circle',
symbol: 'source-' + uid + '-symbol'
};
this.layerIds = {
fill: LAYER_PREFIX + uid + '-fill',
line: LAYER_PREFIX + uid + '-line',
circle: LAYER_PREFIX + uid + '-circle',
symbol: LAYER_PREFIX + uid + '-symbol'
};
// We could merge the 'fill' source with the 'line' source and
// the 'circle' source with the 'symbol' source if ever having
// for up-to 4 sources per 'scattermapbox' traces becomes a problem.
// previous 'below' value,
// need this to update it properly
this.below = null;
}
var proto = ScatterMapbox.prototype;
proto.addSource = function(k, opts) {
this.subplot.map.addSource(this.sourceIds[k], {
type: 'geojson',
data: opts.geojson
});
};
proto.setSourceData = function(k, opts) {
this.subplot.map
.getSource(this.sourceIds[k])
.setData(opts.geojson);
};
proto.addLayer = function(k, opts, below) {
this.subplot.addLayer({
type: k,
id: this.layerIds[k],
source: this.sourceIds[k],
layout: opts.layout,
paint: opts.paint
}, below);
};
proto.update = function update(calcTrace) {
var subplot = this.subplot;
var map = subplot.map;
var optsAll = convert(subplot.gd, calcTrace);
var below = subplot.belowLookup['trace-' + this.uid];
var i, k, opts;
if(below !== this.below) {
for(i = ORDER.length - 1; i >= 0; i--) {
k = ORDER[i];
map.removeLayer(this.layerIds[k]);
}
for(i = 0; i < ORDER.length; i++) {
k = ORDER[i];
opts = optsAll[k];
this.addLayer(k, opts, below);
}
this.below = below;
}
for(i = 0; i < ORDER.length; i++) {
k = ORDER[i];
opts = optsAll[k];
subplot.setOptions(this.layerIds[k], 'setLayoutProperty', opts.layout);
if(opts.layout.visibility === 'visible') {
this.setSourceData(k, opts);
subplot.setOptions(this.layerIds[k], 'setPaintProperty', opts.paint);
}
}
// link ref for quick update during selections
calcTrace[0].trace._glTrace = this;
};
proto.dispose = function dispose() {
var map = this.subplot.map;
for(var i = ORDER.length - 1; i >= 0; i--) {
var k = ORDER[i];
map.removeLayer(this.layerIds[k]);
map.removeSource(this.sourceIds[k]);
}
};
module.exports = function createScatterMapbox(subplot, calcTrace) {
var trace = calcTrace[0].trace;
var scatterMapbox = new ScatterMapbox(subplot, trace.uid);
var optsAll = convert(subplot.gd, calcTrace);
var below = scatterMapbox.below = subplot.belowLookup['trace-' + trace.uid];
for(var i = 0; i < ORDER.length; i++) {
var k = ORDER[i];
var opts = optsAll[k];
scatterMapbox.addSource(k, opts);
scatterMapbox.addLayer(k, opts, below);
}
// link ref for quick update during selections
calcTrace[0].trace._glTrace = scatterMapbox;
return scatterMapbox;
};
},{"../../plots/mapbox/constants":611,"./convert":991}],998:[function(_dereq_,module,exports){
'use strict';
var Lib = _dereq_('../../lib');
var subtypes = _dereq_('../scatter/subtypes');
var BADNUM = _dereq_('../../constants/numerical').BADNUM;
module.exports = function selectPoints(searchInfo, selectionTester) {
var cd = searchInfo.cd;
var xa = searchInfo.xaxis;
var ya = searchInfo.yaxis;
var selection = [];
var trace = cd[0].trace;
var i;
if(!subtypes.hasMarkers(trace)) return [];
if(selectionTester === false) {
for(i = 0; i < cd.length; i++) {
cd[i].selected = 0;
}
} else {
for(i = 0; i < cd.length; i++) {
var di = cd[i];
var lonlat = di.lonlat;
if(lonlat[0] !== BADNUM) {
var lonlat2 = [Lib.modHalf(lonlat[0], 360), lonlat[1]];
var xy = [xa.c2p(lonlat2), ya.c2p(lonlat2)];
if(selectionTester.contains(xy, null, i, searchInfo)) {
selection.push({
pointNumber: i,
lon: lonlat[0],
lat: lonlat[1]
});
di.selected = 1;
} else {
di.selected = 0;
}
}
}
}
return selection;
};
},{"../../constants/numerical":479,"../../lib":503,"../scatter/subtypes":950}],999:[function(_dereq_,module,exports){
'use strict';
var hovertemplateAttrs = _dereq_('../../plots/template_attributes').hovertemplateAttrs;
var texttemplateAttrs = _dereq_('../../plots/template_attributes').texttemplateAttrs;
var extendFlat = _dereq_('../../lib/extend').extendFlat;
var scatterAttrs = _dereq_('../scatter/attributes');
var baseAttrs = _dereq_('../../plots/attributes');
var lineAttrs = scatterAttrs.line;
module.exports = {
mode: scatterAttrs.mode,
r: {
valType: 'data_array',
editType: 'calc+clearAxisTypes',
},
theta: {
valType: 'data_array',
editType: 'calc+clearAxisTypes',
},
r0: {
valType: 'any',
dflt: 0,
editType: 'calc+clearAxisTypes',
},
dr: {
valType: 'number',
dflt: 1,
editType: 'calc',
},
theta0: {
valType: 'any',
dflt: 0,
editType: 'calc+clearAxisTypes',
},
dtheta: {
valType: 'number',
editType: 'calc',
},
thetaunit: {
valType: 'enumerated',
values: ['radians', 'degrees', 'gradians'],
dflt: 'degrees',
editType: 'calc+clearAxisTypes',
},
text: scatterAttrs.text,
texttemplate: texttemplateAttrs({editType: 'plot'}, {
keys: ['r', 'theta', 'text']
}),
hovertext: scatterAttrs.hovertext,
line: {
color: lineAttrs.color,
width: lineAttrs.width,
dash: lineAttrs.dash,
shape: extendFlat({}, lineAttrs.shape, {
values: ['linear', 'spline']
}),
smoothing: lineAttrs.smoothing,
editType: 'calc'
},
connectgaps: scatterAttrs.connectgaps,
marker: scatterAttrs.marker,
cliponaxis: extendFlat({}, scatterAttrs.cliponaxis, {dflt: false}),
textposition: scatterAttrs.textposition,
textfont: scatterAttrs.textfont,
fill: extendFlat({}, scatterAttrs.fill, {
values: ['none', 'toself', 'tonext'],
dflt: 'none',
}),
fillcolor: scatterAttrs.fillcolor,
// TODO error bars
// https://stackoverflow.com/a/26597487/4068492
// error_x (error_r, error_theta)
// error_y
hoverinfo: extendFlat({}, baseAttrs.hoverinfo, {
flags: ['r', 'theta', 'text', 'name']
}),
hoveron: scatterAttrs.hoveron,
hovertemplate: hovertemplateAttrs(),
selected: scatterAttrs.selected,
unselected: scatterAttrs.unselected
};
},{"../../lib/extend":493,"../../plots/attributes":550,"../../plots/template_attributes":633,"../scatter/attributes":925}],1000:[function(_dereq_,module,exports){
'use strict';
var isNumeric = _dereq_('fast-isnumeric');
var BADNUM = _dereq_('../../constants/numerical').BADNUM;
var Axes = _dereq_('../../plots/cartesian/axes');
var calcColorscale = _dereq_('../scatter/colorscale_calc');
var arraysToCalcdata = _dereq_('../scatter/arrays_to_calcdata');
var calcSelection = _dereq_('../scatter/calc_selection');
var calcMarkerSize = _dereq_('../scatter/calc').calcMarkerSize;
module.exports = function calc(gd, trace) {
var fullLayout = gd._fullLayout;
var subplotId = trace.subplot;
var radialAxis = fullLayout[subplotId].radialaxis;
var angularAxis = fullLayout[subplotId].angularaxis;
var rArray = radialAxis.makeCalcdata(trace, 'r');
var thetaArray = angularAxis.makeCalcdata(trace, 'theta');
var len = trace._length;
var cd = new Array(len);
for(var i = 0; i < len; i++) {
var r = rArray[i];
var theta = thetaArray[i];
var cdi = cd[i] = {};
if(isNumeric(r) && isNumeric(theta)) {
cdi.r = r;
cdi.theta = theta;
} else {
cdi.r = BADNUM;
}
}
var ppad = calcMarkerSize(trace, len);
trace._extremes.x = Axes.findExtremes(radialAxis, rArray, {ppad: ppad});
calcColorscale(gd, trace);
arraysToCalcdata(cd, trace);
calcSelection(cd, trace);
return cd;
};
},{"../../constants/numerical":479,"../../plots/cartesian/axes":554,"../scatter/arrays_to_calcdata":924,"../scatter/calc":926,"../scatter/calc_selection":927,"../scatter/colorscale_calc":928,"fast-isnumeric":190}],1001:[function(_dereq_,module,exports){
'use strict';
var Lib = _dereq_('../../lib');
var subTypes = _dereq_('../scatter/subtypes');
var handleMarkerDefaults = _dereq_('../scatter/marker_defaults');
var handleLineDefaults = _dereq_('../scatter/line_defaults');
var handleLineShapeDefaults = _dereq_('../scatter/line_shape_defaults');
var handleTextDefaults = _dereq_('../scatter/text_defaults');
var handleFillColorDefaults = _dereq_('../scatter/fillcolor_defaults');
var PTS_LINESONLY = _dereq_('../scatter/constants').PTS_LINESONLY;
var attributes = _dereq_('./attributes');
function supplyDefaults(traceIn, traceOut, defaultColor, layout) {
function coerce(attr, dflt) {
return Lib.coerce(traceIn, traceOut, attributes, attr, dflt);
}
var len = handleRThetaDefaults(traceIn, traceOut, layout, coerce);
if(!len) {
traceOut.visible = false;
return;
}
coerce('thetaunit');
coerce('mode', len < PTS_LINESONLY ? 'lines+markers' : 'lines');
coerce('text');
coerce('hovertext');
if(traceOut.hoveron !== 'fills') coerce('hovertemplate');
if(subTypes.hasLines(traceOut)) {
handleLineDefaults(traceIn, traceOut, defaultColor, layout, coerce);
handleLineShapeDefaults(traceIn, traceOut, coerce);
coerce('connectgaps');
}
if(subTypes.hasMarkers(traceOut)) {
handleMarkerDefaults(traceIn, traceOut, defaultColor, layout, coerce, {gradient: true});
}
if(subTypes.hasText(traceOut)) {
coerce('texttemplate');
handleTextDefaults(traceIn, traceOut, layout, coerce);
}
var dfltHoverOn = [];
if(subTypes.hasMarkers(traceOut) || subTypes.hasText(traceOut)) {
coerce('cliponaxis');
coerce('marker.maxdisplayed');
dfltHoverOn.push('points');
}
coerce('fill');
if(traceOut.fill !== 'none') {
handleFillColorDefaults(traceIn, traceOut, defaultColor, coerce);
if(!subTypes.hasLines(traceOut)) handleLineShapeDefaults(traceIn, traceOut, coerce);
}
if(traceOut.fill === 'tonext' || traceOut.fill === 'toself') {
dfltHoverOn.push('fills');
}
coerce('hoveron', dfltHoverOn.join('+') || 'points');
Lib.coerceSelectionMarkerOpacity(traceOut, coerce);
}
function handleRThetaDefaults(traceIn, traceOut, layout, coerce) {
var r = coerce('r');
var theta = coerce('theta');
var len;
if(r) {
if(theta) {
len = Math.min(r.length, theta.length);
} else {
len = r.length;
coerce('theta0');
coerce('dtheta');
}
} else {
if(!theta) return 0;
len = traceOut.theta.length;
coerce('r0');
coerce('dr');
}
traceOut._length = len;
return len;
}
module.exports = {
handleRThetaDefaults: handleRThetaDefaults,
supplyDefaults: supplyDefaults
};
},{"../../lib":503,"../scatter/constants":929,"../scatter/fillcolor_defaults":933,"../scatter/line_defaults":938,"../scatter/line_shape_defaults":940,"../scatter/marker_defaults":944,"../scatter/subtypes":950,"../scatter/text_defaults":951,"./attributes":999}],1002:[function(_dereq_,module,exports){
'use strict';
var Lib = _dereq_('../../lib');
var Axes = _dereq_('../../plots/cartesian/axes');
module.exports = function formatLabels(cdi, trace, fullLayout) {
var labels = {};
var subplot = fullLayout[trace.subplot]._subplot;
var radialAxis;
var angularAxis;
// for scatterpolargl texttemplate, _subplot is NOT defined, this takes part during the convert step
// TODO we should consider moving the texttemplate formatting logic to the plot step
if(!subplot) {
subplot = fullLayout[trace.subplot];
radialAxis = subplot.radialaxis;
angularAxis = subplot.angularaxis;
} else {
radialAxis = subplot.radialAxis;
angularAxis = subplot.angularAxis;
}
var rVal = radialAxis.c2l(cdi.r);
labels.rLabel = Axes.tickText(radialAxis, rVal, true).text;
// N.B here the ° sign is part of the formatted value for thetaunit:'degrees'
var thetaVal = angularAxis.thetaunit === 'degrees' ? Lib.rad2deg(cdi.theta) : cdi.theta;
labels.thetaLabel = Axes.tickText(angularAxis, thetaVal, true).text;
return labels;
};
},{"../../lib":503,"../../plots/cartesian/axes":554}],1003:[function(_dereq_,module,exports){
'use strict';
var scatterHover = _dereq_('../scatter/hover');
function hoverPoints(pointData, xval, yval, hovermode) {
var scatterPointData = scatterHover(pointData, xval, yval, hovermode);
if(!scatterPointData || scatterPointData[0].index === false) return;
var newPointData = scatterPointData[0];
// hovering on fill case
if(newPointData.index === undefined) {
return scatterPointData;
}
var subplot = pointData.subplot;
var cdi = newPointData.cd[newPointData.index];
var trace = newPointData.trace;
if(!subplot.isPtInside(cdi)) return;
newPointData.xLabelVal = undefined;
newPointData.yLabelVal = undefined;
makeHoverPointText(cdi, trace, subplot, newPointData);
newPointData.hovertemplate = trace.hovertemplate;
return scatterPointData;
}
function makeHoverPointText(cdi, trace, subplot, pointData) {
var radialAxis = subplot.radialAxis;
var angularAxis = subplot.angularAxis;
radialAxis._hovertitle = 'r';
angularAxis._hovertitle = 'θ';
var fullLayout = {};
fullLayout[trace.subplot] = {_subplot: subplot};
var labels = trace._module.formatLabels(cdi, trace, fullLayout);
pointData.rLabel = labels.rLabel;
pointData.thetaLabel = labels.thetaLabel;
var hoverinfo = cdi.hi || trace.hoverinfo;
var text = [];
function textPart(ax, val) {
text.push(ax._hovertitle + ': ' + val);
}
if(!trace.hovertemplate) {
var parts = hoverinfo.split('+');
if(parts.indexOf('all') !== -1) parts = ['r', 'theta', 'text'];
if(parts.indexOf('r') !== -1) textPart(radialAxis, pointData.rLabel);
if(parts.indexOf('theta') !== -1) textPart(angularAxis, pointData.thetaLabel);
if(parts.indexOf('text') !== -1 && pointData.text) {
text.push(pointData.text);
delete pointData.text;
}
pointData.extraText = text.join('
');
}
}
module.exports = {
hoverPoints: hoverPoints,
makeHoverPointText: makeHoverPointText
};
},{"../scatter/hover":936}],1004:[function(_dereq_,module,exports){
'use strict';
module.exports = {
moduleType: 'trace',
name: 'scatterpolar',
basePlotModule: _dereq_('../../plots/polar'),
categories: ['polar', 'symbols', 'showLegend', 'scatter-like'],
attributes: _dereq_('./attributes'),
supplyDefaults: _dereq_('./defaults').supplyDefaults,
colorbar: _dereq_('../scatter/marker_colorbar'),
formatLabels: _dereq_('./format_labels'),
calc: _dereq_('./calc'),
plot: _dereq_('./plot'),
style: _dereq_('../scatter/style').style,
styleOnSelect: _dereq_('../scatter/style').styleOnSelect,
hoverPoints: _dereq_('./hover').hoverPoints,
selectPoints: _dereq_('../scatter/select'),
meta: {
}
};
},{"../../plots/polar":622,"../scatter/marker_colorbar":943,"../scatter/select":947,"../scatter/style":949,"./attributes":999,"./calc":1000,"./defaults":1001,"./format_labels":1002,"./hover":1003,"./plot":1005}],1005:[function(_dereq_,module,exports){
'use strict';
var scatterPlot = _dereq_('../scatter/plot');
var BADNUM = _dereq_('../../constants/numerical').BADNUM;
module.exports = function plot(gd, subplot, moduleCalcData) {
var mlayer = subplot.layers.frontplot.select('g.scatterlayer');
var plotinfo = {
xaxis: subplot.xaxis,
yaxis: subplot.yaxis,
plot: subplot.framework,
layerClipId: subplot._hasClipOnAxisFalse ? subplot.clipIds.forTraces : null
};
var radialAxis = subplot.radialAxis;
var angularAxis = subplot.angularAxis;
// convert:
// 'c' (r,theta) -> 'geometric' (r,theta) -> (x,y)
for(var i = 0; i < moduleCalcData.length; i++) {
var cdi = moduleCalcData[i];
for(var j = 0; j < cdi.length; j++) {
var cd = cdi[j];
var r = cd.r;
if(r === BADNUM) {
cd.x = cd.y = BADNUM;
} else {
var rg = radialAxis.c2g(r);
var thetag = angularAxis.c2g(cd.theta);
cd.x = rg * Math.cos(thetag);
cd.y = rg * Math.sin(thetag);
}
}
}
scatterPlot(gd, plotinfo, moduleCalcData, mlayer);
};
},{"../../constants/numerical":479,"../scatter/plot":946}],1006:[function(_dereq_,module,exports){
'use strict';
var scatterPolarAttrs = _dereq_('../scatterpolar/attributes');
var scatterGlAttrs = _dereq_('../scattergl/attributes');
var texttemplateAttrs = _dereq_('../../plots/template_attributes').texttemplateAttrs;
module.exports = {
mode: scatterPolarAttrs.mode,
r: scatterPolarAttrs.r,
theta: scatterPolarAttrs.theta,
r0: scatterPolarAttrs.r0,
dr: scatterPolarAttrs.dr,
theta0: scatterPolarAttrs.theta0,
dtheta: scatterPolarAttrs.dtheta,
thetaunit: scatterPolarAttrs.thetaunit,
text: scatterPolarAttrs.text,
texttemplate: texttemplateAttrs({editType: 'plot'}, {
keys: ['r', 'theta', 'text']
}),
hovertext: scatterPolarAttrs.hovertext,
hovertemplate: scatterPolarAttrs.hovertemplate,
line: scatterGlAttrs.line,
connectgaps: scatterGlAttrs.connectgaps,
marker: scatterGlAttrs.marker,
// no cliponaxis
fill: scatterGlAttrs.fill,
fillcolor: scatterGlAttrs.fillcolor,
textposition: scatterGlAttrs.textposition,
textfont: scatterGlAttrs.textfont,
hoverinfo: scatterPolarAttrs.hoverinfo,
// no hoveron
selected: scatterPolarAttrs.selected,
unselected: scatterPolarAttrs.unselected
};
},{"../../plots/template_attributes":633,"../scattergl/attributes":977,"../scatterpolar/attributes":999}],1007:[function(_dereq_,module,exports){
'use strict';
var calcColorscale = _dereq_('../scatter/colorscale_calc');
var calcMarkerSize = _dereq_('../scatter/calc').calcMarkerSize;
var convert = _dereq_('../scattergl/convert');
var Axes = _dereq_('../../plots/cartesian/axes');
var TOO_MANY_POINTS = _dereq_('../scattergl/constants').TOO_MANY_POINTS;
module.exports = function calc(gd, trace) {
var fullLayout = gd._fullLayout;
var subplotId = trace.subplot;
var radialAxis = fullLayout[subplotId].radialaxis;
var angularAxis = fullLayout[subplotId].angularaxis;
var rArray = trace._r = radialAxis.makeCalcdata(trace, 'r');
var thetaArray = trace._theta = angularAxis.makeCalcdata(trace, 'theta');
var len = trace._length;
var stash = {};
if(len < rArray.length) rArray = rArray.slice(0, len);
if(len < thetaArray.length) thetaArray = thetaArray.slice(0, len);
stash.r = rArray;
stash.theta = thetaArray;
calcColorscale(gd, trace);
// only compute 'style' options in calc, as position options
// depend on the radial range and must be set in plot
var opts = stash.opts = convert.style(gd, trace);
// For graphs with very large number of points and array marker.size,
// use average marker size instead to speed things up.
var ppad;
if(len < TOO_MANY_POINTS) {
ppad = calcMarkerSize(trace, len);
} else if(opts.marker) {
ppad = 2 * (opts.marker.sizeAvg || Math.max(opts.marker.size, 3));
}
trace._extremes.x = Axes.findExtremes(radialAxis, rArray, {ppad: ppad});
return [{x: false, y: false, t: stash, trace: trace}];
};
},{"../../plots/cartesian/axes":554,"../scatter/calc":926,"../scatter/colorscale_calc":928,"../scattergl/constants":979,"../scattergl/convert":980}],1008:[function(_dereq_,module,exports){
'use strict';
var Lib = _dereq_('../../lib');
var subTypes = _dereq_('../scatter/subtypes');
var handleRThetaDefaults = _dereq_('../scatterpolar/defaults').handleRThetaDefaults;
var handleMarkerDefaults = _dereq_('../scatter/marker_defaults');
var handleLineDefaults = _dereq_('../scatter/line_defaults');
var handleTextDefaults = _dereq_('../scatter/text_defaults');
var handleFillColorDefaults = _dereq_('../scatter/fillcolor_defaults');
var PTS_LINESONLY = _dereq_('../scatter/constants').PTS_LINESONLY;
var attributes = _dereq_('./attributes');
module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout) {
function coerce(attr, dflt) {
return Lib.coerce(traceIn, traceOut, attributes, attr, dflt);
}
var len = handleRThetaDefaults(traceIn, traceOut, layout, coerce);
if(!len) {
traceOut.visible = false;
return;
}
coerce('thetaunit');
coerce('mode', len < PTS_LINESONLY ? 'lines+markers' : 'lines');
coerce('text');
coerce('hovertext');
if(traceOut.hoveron !== 'fills') coerce('hovertemplate');
if(subTypes.hasLines(traceOut)) {
handleLineDefaults(traceIn, traceOut, defaultColor, layout, coerce);
coerce('connectgaps');
}
if(subTypes.hasMarkers(traceOut)) {
handleMarkerDefaults(traceIn, traceOut, defaultColor, layout, coerce);
}
if(subTypes.hasText(traceOut)) {
coerce('texttemplate');
handleTextDefaults(traceIn, traceOut, layout, coerce);
}
coerce('fill');
if(traceOut.fill !== 'none') {
handleFillColorDefaults(traceIn, traceOut, defaultColor, coerce);
}
Lib.coerceSelectionMarkerOpacity(traceOut, coerce);
};
},{"../../lib":503,"../scatter/constants":929,"../scatter/fillcolor_defaults":933,"../scatter/line_defaults":938,"../scatter/marker_defaults":944,"../scatter/subtypes":950,"../scatter/text_defaults":951,"../scatterpolar/defaults":1001,"./attributes":1006}],1009:[function(_dereq_,module,exports){
'use strict';
var scatterPolarFormatLabels = _dereq_('../scatterpolar/format_labels');
module.exports = function formatLabels(cdi, trace, fullLayout) {
var i = cdi.i;
if(!('r' in cdi)) cdi.r = trace._r[i];
if(!('theta' in cdi)) cdi.theta = trace._theta[i];
return scatterPolarFormatLabels(cdi, trace, fullLayout);
};
},{"../scatterpolar/format_labels":1002}],1010:[function(_dereq_,module,exports){
'use strict';
var hover = _dereq_('../scattergl/hover');
var makeHoverPointText = _dereq_('../scatterpolar/hover').makeHoverPointText;
function hoverPoints(pointData, xval, yval, hovermode) {
var cd = pointData.cd;
var stash = cd[0].t;
var rArray = stash.r;
var thetaArray = stash.theta;
var scatterPointData = hover.hoverPoints(pointData, xval, yval, hovermode);
if(!scatterPointData || scatterPointData[0].index === false) return;
var newPointData = scatterPointData[0];
if(newPointData.index === undefined) {
return scatterPointData;
}
var subplot = pointData.subplot;
var cdi = newPointData.cd[newPointData.index];
var trace = newPointData.trace;
// augment pointData with r/theta param
cdi.r = rArray[newPointData.index];
cdi.theta = thetaArray[newPointData.index];
if(!subplot.isPtInside(cdi)) return;
newPointData.xLabelVal = undefined;
newPointData.yLabelVal = undefined;
makeHoverPointText(cdi, trace, subplot, newPointData);
return scatterPointData;
}
module.exports = {
hoverPoints: hoverPoints
};
},{"../scattergl/hover":985,"../scatterpolar/hover":1003}],1011:[function(_dereq_,module,exports){
'use strict';
module.exports = {
moduleType: 'trace',
name: 'scatterpolargl',
basePlotModule: _dereq_('../../plots/polar'),
categories: ['gl', 'regl', 'polar', 'symbols', 'showLegend', 'scatter-like'],
attributes: _dereq_('./attributes'),
supplyDefaults: _dereq_('./defaults'),
colorbar: _dereq_('../scatter/marker_colorbar'),
formatLabels: _dereq_('./format_labels'),
calc: _dereq_('./calc'),
plot: _dereq_('./plot'),
hoverPoints: _dereq_('./hover').hoverPoints,
selectPoints: _dereq_('../scattergl/select'),
meta: {
}
};
},{"../../plots/polar":622,"../scatter/marker_colorbar":943,"../scattergl/select":989,"./attributes":1006,"./calc":1007,"./defaults":1008,"./format_labels":1009,"./hover":1010,"./plot":1012}],1012:[function(_dereq_,module,exports){
'use strict';
var cluster = _dereq_('@plotly/point-cluster');
var isNumeric = _dereq_('fast-isnumeric');
var scatterglPlot = _dereq_('../scattergl/plot');
var sceneUpdate = _dereq_('../scattergl/scene_update');
var convert = _dereq_('../scattergl/convert');
var Lib = _dereq_('../../lib');
var TOO_MANY_POINTS = _dereq_('../scattergl/constants').TOO_MANY_POINTS;
module.exports = function plot(gd, subplot, cdata) {
if(!cdata.length) return;
var radialAxis = subplot.radialAxis;
var angularAxis = subplot.angularAxis;
var scene = sceneUpdate(gd, subplot);
cdata.forEach(function(cdscatter) {
if(!cdscatter || !cdscatter[0] || !cdscatter[0].trace) return;
var cd = cdscatter[0];
var trace = cd.trace;
var stash = cd.t;
var len = trace._length;
var rArray = stash.r;
var thetaArray = stash.theta;
var opts = stash.opts;
var i;
var subRArray = rArray.slice();
var subThetaArray = thetaArray.slice();
// filter out by range
for(i = 0; i < rArray.length; i++) {
if(!subplot.isPtInside({r: rArray[i], theta: thetaArray[i]})) {
subRArray[i] = NaN;
subThetaArray[i] = NaN;
}
}
var positions = new Array(len * 2);
var x = Array(len);
var y = Array(len);
for(i = 0; i < len; i++) {
var r = subRArray[i];
var xx, yy;
if(isNumeric(r)) {
var rg = radialAxis.c2g(r);
var thetag = angularAxis.c2g(subThetaArray[i], trace.thetaunit);
xx = rg * Math.cos(thetag);
yy = rg * Math.sin(thetag);
} else {
xx = yy = NaN;
}
x[i] = positions[i * 2] = xx;
y[i] = positions[i * 2 + 1] = yy;
}
stash.tree = cluster(positions);
// FIXME: see scattergl.js#109
if(opts.marker && len >= TOO_MANY_POINTS) {
opts.marker.cluster = stash.tree;
}
if(opts.marker) {
opts.markerSel.positions = opts.markerUnsel.positions = opts.marker.positions = positions;
}
if(opts.line && positions.length > 1) {
Lib.extendFlat(
opts.line,
convert.linePositions(gd, trace, positions)
);
}
if(opts.text) {
Lib.extendFlat(
opts.text,
{positions: positions},
convert.textPosition(gd, trace, opts.text, opts.marker)
);
Lib.extendFlat(
opts.textSel,
{positions: positions},
convert.textPosition(gd, trace, opts.text, opts.markerSel)
);
Lib.extendFlat(
opts.textUnsel,
{positions: positions},
convert.textPosition(gd, trace, opts.text, opts.markerUnsel)
);
}
if(opts.fill && !scene.fill2d) scene.fill2d = true;
if(opts.marker && !scene.scatter2d) scene.scatter2d = true;
if(opts.line && !scene.line2d) scene.line2d = true;
if(opts.text && !scene.glText) scene.glText = true;
scene.lineOptions.push(opts.line);
scene.fillOptions.push(opts.fill);
scene.markerOptions.push(opts.marker);
scene.markerSelectedOptions.push(opts.markerSel);
scene.markerUnselectedOptions.push(opts.markerUnsel);
scene.textOptions.push(opts.text);
scene.textSelectedOptions.push(opts.textSel);
scene.textUnselectedOptions.push(opts.textUnsel);
scene.selectBatch.push([]);
scene.unselectBatch.push([]);
stash.x = x;
stash.y = y;
stash.rawx = x;
stash.rawy = y;
stash.r = rArray;
stash.theta = thetaArray;
stash.positions = positions;
stash._scene = scene;
stash.index = scene.count;
scene.count++;
});
return scatterglPlot(gd, subplot, cdata);
};
},{"../../lib":503,"../scattergl/constants":979,"../scattergl/convert":980,"../scattergl/plot":987,"../scattergl/scene_update":988,"@plotly/point-cluster":59,"fast-isnumeric":190}],1013:[function(_dereq_,module,exports){
'use strict';
var hovertemplateAttrs = _dereq_('../../plots/template_attributes').hovertemplateAttrs;
var texttemplateAttrs = _dereq_('../../plots/template_attributes').texttemplateAttrs;
var extendFlat = _dereq_('../../lib/extend').extendFlat;
var scatterAttrs = _dereq_('../scatter/attributes');
var baseAttrs = _dereq_('../../plots/attributes');
var lineAttrs = scatterAttrs.line;
module.exports = {
mode: scatterAttrs.mode,
real: {
valType: 'data_array',
editType: 'calc+clearAxisTypes',
},
imag: {
valType: 'data_array',
editType: 'calc+clearAxisTypes',
},
text: scatterAttrs.text,
texttemplate: texttemplateAttrs({editType: 'plot'}, {
keys: ['real', 'imag', 'text']
}),
hovertext: scatterAttrs.hovertext,
line: {
color: lineAttrs.color,
width: lineAttrs.width,
dash: lineAttrs.dash,
shape: extendFlat({}, lineAttrs.shape, {
values: ['linear', 'spline']
}),
smoothing: lineAttrs.smoothing,
editType: 'calc'
},
connectgaps: scatterAttrs.connectgaps,
marker: scatterAttrs.marker,
cliponaxis: extendFlat({}, scatterAttrs.cliponaxis, {dflt: false}),
textposition: scatterAttrs.textposition,
textfont: scatterAttrs.textfont,
fill: extendFlat({}, scatterAttrs.fill, {
values: ['none', 'toself', 'tonext'],
dflt: 'none',
}),
fillcolor: scatterAttrs.fillcolor,
hoverinfo: extendFlat({}, baseAttrs.hoverinfo, {
flags: ['real', 'imag', 'text', 'name']
}),
hoveron: scatterAttrs.hoveron,
hovertemplate: hovertemplateAttrs(),
selected: scatterAttrs.selected,
unselected: scatterAttrs.unselected
};
},{"../../lib/extend":493,"../../plots/attributes":550,"../../plots/template_attributes":633,"../scatter/attributes":925}],1014:[function(_dereq_,module,exports){
'use strict';
var isNumeric = _dereq_('fast-isnumeric');
var BADNUM = _dereq_('../../constants/numerical').BADNUM;
var calcColorscale = _dereq_('../scatter/colorscale_calc');
var arraysToCalcdata = _dereq_('../scatter/arrays_to_calcdata');
var calcSelection = _dereq_('../scatter/calc_selection');
var calcMarkerSize = _dereq_('../scatter/calc').calcMarkerSize;
module.exports = function calc(gd, trace) {
var fullLayout = gd._fullLayout;
var subplotId = trace.subplot;
var realAxis = fullLayout[subplotId].realaxis;
var imaginaryAxis = fullLayout[subplotId].imaginaryaxis;
var realArray = realAxis.makeCalcdata(trace, 'real');
var imagArray = imaginaryAxis.makeCalcdata(trace, 'imag');
var len = trace._length;
var cd = new Array(len);
for(var i = 0; i < len; i++) {
var real = realArray[i];
var imag = imagArray[i];
var cdi = cd[i] = {};
if(isNumeric(real) && isNumeric(imag)) {
cdi.real = real;
cdi.imag = imag;
} else {
cdi.real = BADNUM;
}
}
calcMarkerSize(trace, len);
calcColorscale(gd, trace);
arraysToCalcdata(cd, trace);
calcSelection(cd, trace);
return cd;
};
},{"../../constants/numerical":479,"../scatter/arrays_to_calcdata":924,"../scatter/calc":926,"../scatter/calc_selection":927,"../scatter/colorscale_calc":928,"fast-isnumeric":190}],1015:[function(_dereq_,module,exports){
'use strict';
var Lib = _dereq_('../../lib');
var subTypes = _dereq_('../scatter/subtypes');
var handleMarkerDefaults = _dereq_('../scatter/marker_defaults');
var handleLineDefaults = _dereq_('../scatter/line_defaults');
var handleLineShapeDefaults = _dereq_('../scatter/line_shape_defaults');
var handleTextDefaults = _dereq_('../scatter/text_defaults');
var handleFillColorDefaults = _dereq_('../scatter/fillcolor_defaults');
var PTS_LINESONLY = _dereq_('../scatter/constants').PTS_LINESONLY;
var attributes = _dereq_('./attributes');
module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout) {
function coerce(attr, dflt) {
return Lib.coerce(traceIn, traceOut, attributes, attr, dflt);
}
var len = handleRealImagDefaults(traceIn, traceOut, layout, coerce);
if(!len) {
traceOut.visible = false;
return;
}
coerce('mode', len < PTS_LINESONLY ? 'lines+markers' : 'lines');
coerce('text');
coerce('hovertext');
if(traceOut.hoveron !== 'fills') coerce('hovertemplate');
if(subTypes.hasLines(traceOut)) {
handleLineDefaults(traceIn, traceOut, defaultColor, layout, coerce);
handleLineShapeDefaults(traceIn, traceOut, coerce);
coerce('connectgaps');
}
if(subTypes.hasMarkers(traceOut)) {
handleMarkerDefaults(traceIn, traceOut, defaultColor, layout, coerce, {gradient: true});
}
if(subTypes.hasText(traceOut)) {
coerce('texttemplate');
handleTextDefaults(traceIn, traceOut, layout, coerce);
}
var dfltHoverOn = [];
if(subTypes.hasMarkers(traceOut) || subTypes.hasText(traceOut)) {
coerce('cliponaxis');
coerce('marker.maxdisplayed');
dfltHoverOn.push('points');
}
coerce('fill');
if(traceOut.fill !== 'none') {
handleFillColorDefaults(traceIn, traceOut, defaultColor, coerce);
if(!subTypes.hasLines(traceOut)) handleLineShapeDefaults(traceIn, traceOut, coerce);
}
if(traceOut.fill === 'tonext' || traceOut.fill === 'toself') {
dfltHoverOn.push('fills');
}
coerce('hoveron', dfltHoverOn.join('+') || 'points');
Lib.coerceSelectionMarkerOpacity(traceOut, coerce);
};
function handleRealImagDefaults(traceIn, traceOut, layout, coerce) {
var real = coerce('real');
var imag = coerce('imag');
var len;
if(real && imag) {
len = Math.min(real.length, imag.length);
}
traceOut._length = len;
return len;
}
},{"../../lib":503,"../scatter/constants":929,"../scatter/fillcolor_defaults":933,"../scatter/line_defaults":938,"../scatter/line_shape_defaults":940,"../scatter/marker_defaults":944,"../scatter/subtypes":950,"../scatter/text_defaults":951,"./attributes":1013}],1016:[function(_dereq_,module,exports){
'use strict';
var Axes = _dereq_('../../plots/cartesian/axes');
module.exports = function formatLabels(cdi, trace, fullLayout) {
var labels = {};
var subplot = fullLayout[trace.subplot]._subplot;
labels.realLabel = Axes.tickText(subplot.radialAxis, cdi.real, true).text;
labels.imagLabel = Axes.tickText(subplot.angularAxis, cdi.imag, true).text;
return labels;
};
},{"../../plots/cartesian/axes":554}],1017:[function(_dereq_,module,exports){
'use strict';
var scatterHover = _dereq_('../scatter/hover');
function hoverPoints(pointData, xval, yval, hovermode) {
var scatterPointData = scatterHover(pointData, xval, yval, hovermode);
if(!scatterPointData || scatterPointData[0].index === false) return;
var newPointData = scatterPointData[0];
// hovering on fill case
if(newPointData.index === undefined) {
return scatterPointData;
}
var subplot = pointData.subplot;
var cdi = newPointData.cd[newPointData.index];
var trace = newPointData.trace;
if(!subplot.isPtInside(cdi)) return;
newPointData.xLabelVal = undefined;
newPointData.yLabelVal = undefined;
makeHoverPointText(cdi, trace, subplot, newPointData);
newPointData.hovertemplate = trace.hovertemplate;
return scatterPointData;
}
function makeHoverPointText(cdi, trace, subplot, pointData) {
var realAxis = subplot.radialAxis;
var imaginaryAxis = subplot.angularAxis;
realAxis._hovertitle = 'real';
imaginaryAxis._hovertitle = 'imag';
var fullLayout = {};
fullLayout[trace.subplot] = {_subplot: subplot};
var labels = trace._module.formatLabels(cdi, trace, fullLayout);
pointData.realLabel = labels.realLabel;
pointData.imagLabel = labels.imagLabel;
var hoverinfo = cdi.hi || trace.hoverinfo;
var text = [];
function textPart(ax, val) {
text.push(ax._hovertitle + ': ' + val);
}
if(!trace.hovertemplate) {
var parts = hoverinfo.split('+');
if(parts.indexOf('all') !== -1) parts = ['real', 'imag', 'text'];
if(parts.indexOf('real') !== -1) textPart(realAxis, pointData.realLabel);
if(parts.indexOf('imag') !== -1) textPart(imaginaryAxis, pointData.imagLabel);
if(parts.indexOf('text') !== -1 && pointData.text) {
text.push(pointData.text);
delete pointData.text;
}
pointData.extraText = text.join('
');
}
}
module.exports = {
hoverPoints: hoverPoints,
makeHoverPointText: makeHoverPointText
};
},{"../scatter/hover":936}],1018:[function(_dereq_,module,exports){
'use strict';
module.exports = {
moduleType: 'trace',
name: 'scattersmith',
basePlotModule: _dereq_('../../plots/smith'),
categories: ['smith', 'symbols', 'showLegend', 'scatter-like'],
attributes: _dereq_('./attributes'),
supplyDefaults: _dereq_('./defaults'),
colorbar: _dereq_('../scatter/marker_colorbar'),
formatLabels: _dereq_('./format_labels'),
calc: _dereq_('./calc'),
plot: _dereq_('./plot'),
style: _dereq_('../scatter/style').style,
styleOnSelect: _dereq_('../scatter/style').styleOnSelect,
hoverPoints: _dereq_('./hover').hoverPoints,
selectPoints: _dereq_('../scatter/select'),
meta: {
}
};
},{"../../plots/smith":629,"../scatter/marker_colorbar":943,"../scatter/select":947,"../scatter/style":949,"./attributes":1013,"./calc":1014,"./defaults":1015,"./format_labels":1016,"./hover":1017,"./plot":1019}],1019:[function(_dereq_,module,exports){
'use strict';
var scatterPlot = _dereq_('../scatter/plot');
var BADNUM = _dereq_('../../constants/numerical').BADNUM;
var helpers = _dereq_('../../plots/smith/helpers');
var smith = helpers.smith;
module.exports = function plot(gd, subplot, moduleCalcData) {
var mlayer = subplot.layers.frontplot.select('g.scatterlayer');
var plotinfo = {
xaxis: subplot.xaxis,
yaxis: subplot.yaxis,
plot: subplot.framework,
layerClipId: subplot._hasClipOnAxisFalse ? subplot.clipIds.forTraces : null
};
// convert:
// 'c' (real,imag) -> (x,y)
for(var i = 0; i < moduleCalcData.length; i++) {
var cdi = moduleCalcData[i];
for(var j = 0; j < cdi.length; j++) {
var cd = cdi[j];
var real = cd.real;
if(real === BADNUM) {
cd.x = cd.y = BADNUM;
} else {
var t = smith([real, cd.imag]);
cd.x = t[0];
cd.y = t[1];
}
}
}
scatterPlot(gd, plotinfo, moduleCalcData, mlayer);
};
},{"../../constants/numerical":479,"../../plots/smith/helpers":628,"../scatter/plot":946}],1020:[function(_dereq_,module,exports){
'use strict';
var hovertemplateAttrs = _dereq_('../../plots/template_attributes').hovertemplateAttrs;
var texttemplateAttrs = _dereq_('../../plots/template_attributes').texttemplateAttrs;
var scatterAttrs = _dereq_('../scatter/attributes');
var baseAttrs = _dereq_('../../plots/attributes');
var colorScaleAttrs = _dereq_('../../components/colorscale/attributes');
var dash = _dereq_('../../components/drawing/attributes').dash;
var extendFlat = _dereq_('../../lib/extend').extendFlat;
var scatterMarkerAttrs = scatterAttrs.marker;
var scatterLineAttrs = scatterAttrs.line;
var scatterMarkerLineAttrs = scatterMarkerAttrs.line;
module.exports = {
a: {
valType: 'data_array',
editType: 'calc',
},
b: {
valType: 'data_array',
editType: 'calc',
},
c: {
valType: 'data_array',
editType: 'calc',
},
sum: {
valType: 'number',
dflt: 0,
min: 0,
editType: 'calc',
},
mode: extendFlat({}, scatterAttrs.mode, {dflt: 'markers'}),
text: extendFlat({}, scatterAttrs.text, {
}),
texttemplate: texttemplateAttrs({editType: 'plot'}, {
keys: ['a', 'b', 'c', 'text']
}),
hovertext: extendFlat({}, scatterAttrs.hovertext, {
}),
line: {
color: scatterLineAttrs.color,
width: scatterLineAttrs.width,
dash: dash,
shape: extendFlat({}, scatterLineAttrs.shape,
{values: ['linear', 'spline']}),
smoothing: scatterLineAttrs.smoothing,
editType: 'calc'
},
connectgaps: scatterAttrs.connectgaps,
cliponaxis: scatterAttrs.cliponaxis,
fill: extendFlat({}, scatterAttrs.fill, {
values: ['none', 'toself', 'tonext'],
dflt: 'none',
}),
fillcolor: scatterAttrs.fillcolor,
marker: extendFlat({
symbol: scatterMarkerAttrs.symbol,
opacity: scatterMarkerAttrs.opacity,
maxdisplayed: scatterMarkerAttrs.maxdisplayed,
size: scatterMarkerAttrs.size,
sizeref: scatterMarkerAttrs.sizeref,
sizemin: scatterMarkerAttrs.sizemin,
sizemode: scatterMarkerAttrs.sizemode,
line: extendFlat({
width: scatterMarkerLineAttrs.width,
editType: 'calc'
},
colorScaleAttrs('marker.line')
),
gradient: scatterMarkerAttrs.gradient,
editType: 'calc'
},
colorScaleAttrs('marker')
),
textfont: scatterAttrs.textfont,
textposition: scatterAttrs.textposition,
selected: scatterAttrs.selected,
unselected: scatterAttrs.unselected,
hoverinfo: extendFlat({}, baseAttrs.hoverinfo, {
flags: ['a', 'b', 'c', 'text', 'name']
}),
hoveron: scatterAttrs.hoveron,
hovertemplate: hovertemplateAttrs(),
};
},{"../../components/colorscale/attributes":373,"../../components/drawing/attributes":387,"../../lib/extend":493,"../../plots/attributes":550,"../../plots/template_attributes":633,"../scatter/attributes":925}],1021:[function(_dereq_,module,exports){
'use strict';
var isNumeric = _dereq_('fast-isnumeric');
var calcColorscale = _dereq_('../scatter/colorscale_calc');
var arraysToCalcdata = _dereq_('../scatter/arrays_to_calcdata');
var calcSelection = _dereq_('../scatter/calc_selection');
var calcMarkerSize = _dereq_('../scatter/calc').calcMarkerSize;
var dataArrays = ['a', 'b', 'c'];
var arraysToFill = {a: ['b', 'c'], b: ['a', 'c'], c: ['a', 'b']};
module.exports = function calc(gd, trace) {
var ternary = gd._fullLayout[trace.subplot];
var displaySum = ternary.sum;
var normSum = trace.sum || displaySum;
var arrays = {a: trace.a, b: trace.b, c: trace.c};
var i, j, dataArray, newArray, fillArray1, fillArray2;
// fill in one missing component
for(i = 0; i < dataArrays.length; i++) {
dataArray = dataArrays[i];
if(arrays[dataArray]) continue;
fillArray1 = arrays[arraysToFill[dataArray][0]];
fillArray2 = arrays[arraysToFill[dataArray][1]];
newArray = new Array(fillArray1.length);
for(j = 0; j < fillArray1.length; j++) {
newArray[j] = normSum - fillArray1[j] - fillArray2[j];
}
arrays[dataArray] = newArray;
}
// make the calcdata array
var serieslen = trace._length;
var cd = new Array(serieslen);
var a, b, c, norm, x, y;
for(i = 0; i < serieslen; i++) {
a = arrays.a[i];
b = arrays.b[i];
c = arrays.c[i];
if(isNumeric(a) && isNumeric(b) && isNumeric(c)) {
a = +a;
b = +b;
c = +c;
norm = displaySum / (a + b + c);
if(norm !== 1) {
a *= norm;
b *= norm;
c *= norm;
}
// map a, b, c onto x and y where the full scale of y
// is [0, sum], and x is [-sum, sum]
// TODO: this makes `a` always the top, `b` the bottom left,
// and `c` the bottom right. Do we want options to rearrange
// these?
y = a;
x = c - b;
cd[i] = {x: x, y: y, a: a, b: b, c: c};
} else cd[i] = {x: false, y: false};
}
calcMarkerSize(trace, serieslen);
calcColorscale(gd, trace);
arraysToCalcdata(cd, trace);
calcSelection(cd, trace);
return cd;
};
},{"../scatter/arrays_to_calcdata":924,"../scatter/calc":926,"../scatter/calc_selection":927,"../scatter/colorscale_calc":928,"fast-isnumeric":190}],1022:[function(_dereq_,module,exports){
'use strict';
var Lib = _dereq_('../../lib');
var constants = _dereq_('../scatter/constants');
var subTypes = _dereq_('../scatter/subtypes');
var handleMarkerDefaults = _dereq_('../scatter/marker_defaults');
var handleLineDefaults = _dereq_('../scatter/line_defaults');
var handleLineShapeDefaults = _dereq_('../scatter/line_shape_defaults');
var handleTextDefaults = _dereq_('../scatter/text_defaults');
var handleFillColorDefaults = _dereq_('../scatter/fillcolor_defaults');
var attributes = _dereq_('./attributes');
module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout) {
function coerce(attr, dflt) {
return Lib.coerce(traceIn, traceOut, attributes, attr, dflt);
}
var a = coerce('a');
var b = coerce('b');
var c = coerce('c');
var len;
// allow any one array to be missing, len is the minimum length of those
// present. Note that after coerce data_array's are either Arrays (which
// are truthy even if empty) or undefined. As in scatter, an empty array
// is different from undefined, because it can signify that this data is
// not known yet but expected in the future
if(a) {
len = a.length;
if(b) {
len = Math.min(len, b.length);
if(c) len = Math.min(len, c.length);
} else if(c) len = Math.min(len, c.length);
else len = 0;
} else if(b && c) {
len = Math.min(b.length, c.length);
}
if(!len) {
traceOut.visible = false;
return;
}
traceOut._length = len;
coerce('sum');
coerce('text');
coerce('hovertext');
if(traceOut.hoveron !== 'fills') coerce('hovertemplate');
var defaultMode = len < constants.PTS_LINESONLY ? 'lines+markers' : 'lines';
coerce('mode', defaultMode);
if(subTypes.hasLines(traceOut)) {
handleLineDefaults(traceIn, traceOut, defaultColor, layout, coerce);
handleLineShapeDefaults(traceIn, traceOut, coerce);
coerce('connectgaps');
}
if(subTypes.hasMarkers(traceOut)) {
handleMarkerDefaults(traceIn, traceOut, defaultColor, layout, coerce, {gradient: true});
}
if(subTypes.hasText(traceOut)) {
coerce('texttemplate');
handleTextDefaults(traceIn, traceOut, layout, coerce);
}
var dfltHoverOn = [];
if(subTypes.hasMarkers(traceOut) || subTypes.hasText(traceOut)) {
coerce('cliponaxis');
coerce('marker.maxdisplayed');
dfltHoverOn.push('points');
}
coerce('fill');
if(traceOut.fill !== 'none') {
handleFillColorDefaults(traceIn, traceOut, defaultColor, coerce);
if(!subTypes.hasLines(traceOut)) handleLineShapeDefaults(traceIn, traceOut, coerce);
}
if(traceOut.fill === 'tonext' || traceOut.fill === 'toself') {
dfltHoverOn.push('fills');
}
coerce('hoveron', dfltHoverOn.join('+') || 'points');
Lib.coerceSelectionMarkerOpacity(traceOut, coerce);
};
},{"../../lib":503,"../scatter/constants":929,"../scatter/fillcolor_defaults":933,"../scatter/line_defaults":938,"../scatter/line_shape_defaults":940,"../scatter/marker_defaults":944,"../scatter/subtypes":950,"../scatter/text_defaults":951,"./attributes":1020}],1023:[function(_dereq_,module,exports){
'use strict';
module.exports = function eventData(out, pt, trace, cd, pointNumber) {
if(pt.xa) out.xaxis = pt.xa;
if(pt.ya) out.yaxis = pt.ya;
if(cd[pointNumber]) {
var cdi = cd[pointNumber];
// N.B. These are the normalized coordinates.
out.a = cdi.a;
out.b = cdi.b;
out.c = cdi.c;
} else {
// for fill-hover only
out.a = pt.a;
out.b = pt.b;
out.c = pt.c;
}
return out;
};
},{}],1024:[function(_dereq_,module,exports){
'use strict';
var Axes = _dereq_('../../plots/cartesian/axes');
module.exports = function formatLabels(cdi, trace, fullLayout) {
var labels = {};
var subplot = fullLayout[trace.subplot]._subplot;
labels.aLabel = Axes.tickText(subplot.aaxis, cdi.a, true).text;
labels.bLabel = Axes.tickText(subplot.baxis, cdi.b, true).text;
labels.cLabel = Axes.tickText(subplot.caxis, cdi.c, true).text;
return labels;
};
},{"../../plots/cartesian/axes":554}],1025:[function(_dereq_,module,exports){
'use strict';
var scatterHover = _dereq_('../scatter/hover');
module.exports = function hoverPoints(pointData, xval, yval, hovermode) {
var scatterPointData = scatterHover(pointData, xval, yval, hovermode);
if(!scatterPointData || scatterPointData[0].index === false) return;
var newPointData = scatterPointData[0];
// if hovering on a fill, we don't show any point data so the label is
// unchanged from what scatter gives us - except that it needs to
// be constrained to the trianglular plot area, not just the rectangular
// area defined by the synthetic x and y axes
// TODO: in some cases the vertical middle of the shape is not within
// the triangular viewport at all, so the label can become disconnected
// from the shape entirely. But calculating what portion of the shape
// is actually visible, as constrained by the diagonal axis lines, is not
// so easy and anyway we lost the information we would have needed to do
// this inside scatterHover.
if(newPointData.index === undefined) {
var yFracUp = 1 - (newPointData.y0 / pointData.ya._length);
var xLen = pointData.xa._length;
var xMin = xLen * yFracUp / 2;
var xMax = xLen - xMin;
newPointData.x0 = Math.max(Math.min(newPointData.x0, xMax), xMin);
newPointData.x1 = Math.max(Math.min(newPointData.x1, xMax), xMin);
return scatterPointData;
}
var cdi = newPointData.cd[newPointData.index];
var trace = newPointData.trace;
var subplot = newPointData.subplot;
newPointData.a = cdi.a;
newPointData.b = cdi.b;
newPointData.c = cdi.c;
newPointData.xLabelVal = undefined;
newPointData.yLabelVal = undefined;
var fullLayout = {};
fullLayout[trace.subplot] = {_subplot: subplot};
var labels = trace._module.formatLabels(cdi, trace, fullLayout);
newPointData.aLabel = labels.aLabel;
newPointData.bLabel = labels.bLabel;
newPointData.cLabel = labels.cLabel;
var hoverinfo = cdi.hi || trace.hoverinfo;
var text = [];
function textPart(ax, val) {
text.push(ax._hovertitle + ': ' + val);
}
if(!trace.hovertemplate) {
var parts = hoverinfo.split('+');
if(parts.indexOf('all') !== -1) parts = ['a', 'b', 'c'];
if(parts.indexOf('a') !== -1) textPart(subplot.aaxis, newPointData.aLabel);
if(parts.indexOf('b') !== -1) textPart(subplot.baxis, newPointData.bLabel);
if(parts.indexOf('c') !== -1) textPart(subplot.caxis, newPointData.cLabel);
}
newPointData.extraText = text.join('
');
newPointData.hovertemplate = trace.hovertemplate;
return scatterPointData;
};
},{"../scatter/hover":936}],1026:[function(_dereq_,module,exports){
'use strict';
module.exports = {
attributes: _dereq_('./attributes'),
supplyDefaults: _dereq_('./defaults'),
colorbar: _dereq_('../scatter/marker_colorbar'),
formatLabels: _dereq_('./format_labels'),
calc: _dereq_('./calc'),
plot: _dereq_('./plot'),
style: _dereq_('../scatter/style').style,
styleOnSelect: _dereq_('../scatter/style').styleOnSelect,
hoverPoints: _dereq_('./hover'),
selectPoints: _dereq_('../scatter/select'),
eventData: _dereq_('./event_data'),
moduleType: 'trace',
name: 'scatterternary',
basePlotModule: _dereq_('../../plots/ternary'),
categories: ['ternary', 'symbols', 'showLegend', 'scatter-like'],
meta: {
}
};
},{"../../plots/ternary":634,"../scatter/marker_colorbar":943,"../scatter/select":947,"../scatter/style":949,"./attributes":1020,"./calc":1021,"./defaults":1022,"./event_data":1023,"./format_labels":1024,"./hover":1025,"./plot":1027}],1027:[function(_dereq_,module,exports){
'use strict';
var scatterPlot = _dereq_('../scatter/plot');
module.exports = function plot(gd, ternary, moduleCalcData) {
var plotContainer = ternary.plotContainer;
// remove all nodes inside the scatter layer
plotContainer.select('.scatterlayer').selectAll('*').remove();
// mimic cartesian plotinfo
var plotinfo = {
xaxis: ternary.xaxis,
yaxis: ternary.yaxis,
plot: plotContainer,
layerClipId: ternary._hasClipOnAxisFalse ? ternary.clipIdRelative : null
};
var scatterLayer = ternary.layers.frontplot.select('g.scatterlayer');
scatterPlot(gd, plotinfo, moduleCalcData, scatterLayer);
};
},{"../scatter/plot":946}],1028:[function(_dereq_,module,exports){
'use strict';
var scatterAttrs = _dereq_('../scatter/attributes');
var colorScaleAttrs = _dereq_('../../components/colorscale/attributes');
var axisHoverFormat = _dereq_('../../plots/cartesian/axis_format_attributes').axisHoverFormat;
var hovertemplateAttrs = _dereq_('../../plots/template_attributes').hovertemplateAttrs;
var scatterGlAttrs = _dereq_('../scattergl/attributes');
var cartesianIdRegex = _dereq_('../../plots/cartesian/constants').idRegex;
var templatedArray = _dereq_('../../plot_api/plot_template').templatedArray;
var extendFlat = _dereq_('../../lib/extend').extendFlat;
var scatterMarkerAttrs = scatterAttrs.marker;
var scatterMarkerLineAttrs = scatterMarkerAttrs.line;
var markerLineAttrs = extendFlat(colorScaleAttrs('marker.line', {editTypeOverride: 'calc'}), {
width: extendFlat({}, scatterMarkerLineAttrs.width, {editType: 'calc'}),
editType: 'calc'
});
var markerAttrs = extendFlat(colorScaleAttrs('marker'), {
symbol: scatterMarkerAttrs.symbol,
size: extendFlat({}, scatterMarkerAttrs.size, {editType: 'markerSize'}),
sizeref: scatterMarkerAttrs.sizeref,
sizemin: scatterMarkerAttrs.sizemin,
sizemode: scatterMarkerAttrs.sizemode,
opacity: scatterMarkerAttrs.opacity,
colorbar: scatterMarkerAttrs.colorbar,
line: markerLineAttrs,
editType: 'calc'
});
markerAttrs.color.editType = markerAttrs.cmin.editType = markerAttrs.cmax.editType = 'style';
function makeAxesValObject(axLetter) {
return {
valType: 'info_array',
freeLength: true,
editType: 'calc',
items: {
valType: 'subplotid',
regex: cartesianIdRegex[axLetter],
editType: 'plot'
},
};
}
module.exports = {
dimensions: templatedArray('dimension', {
visible: {
valType: 'boolean',
dflt: true,
editType: 'calc',
},
label: {
valType: 'string',
editType: 'calc',
},
values: {
valType: 'data_array',
editType: 'calc+clearAxisTypes',
},
axis: {
type: {
valType: 'enumerated',
values: ['linear', 'log', 'date', 'category'],
editType: 'calc+clearAxisTypes',
},
// TODO make 'true' the default in v3?
matches: {
valType: 'boolean',
dflt: false,
editType: 'calc',
},
editType: 'calc+clearAxisTypes'
},
// TODO should add an attribute to pin down x only vars and y only vars
// like https://seaborn.pydata.org/generated/seaborn.pairplot.html
// x_vars and y_vars
// maybe more axis defaulting option e.g. `showgrid: false`
editType: 'calc+clearAxisTypes'
}),
// mode: {}, (only 'markers' for now)
text: extendFlat({}, scatterGlAttrs.text, {
}),
hovertext: extendFlat({}, scatterGlAttrs.hovertext, {
}),
hovertemplate: hovertemplateAttrs(),
xhoverformat: axisHoverFormat('x'),
yhoverformat: axisHoverFormat('y'),
marker: markerAttrs,
xaxes: makeAxesValObject('x'),
yaxes: makeAxesValObject('y'),
diagonal: {
visible: {
valType: 'boolean',
dflt: true,
editType: 'calc',
},
// type: 'scattergl' | 'histogram' | 'box' | 'violin'
// ...
// more options
editType: 'calc'
},
showupperhalf: {
valType: 'boolean',
dflt: true,
editType: 'calc',
},
showlowerhalf: {
valType: 'boolean',
dflt: true,
editType: 'calc',
},
selected: {
marker: scatterGlAttrs.selected.marker,
editType: 'calc'
},
unselected: {
marker: scatterGlAttrs.unselected.marker,
editType: 'calc'
},
opacity: scatterGlAttrs.opacity
};
},{"../../components/colorscale/attributes":373,"../../lib/extend":493,"../../plot_api/plot_template":543,"../../plots/cartesian/axis_format_attributes":557,"../../plots/cartesian/constants":561,"../../plots/template_attributes":633,"../scatter/attributes":925,"../scattergl/attributes":977}],1029:[function(_dereq_,module,exports){
'use strict';
var createLine = _dereq_('regl-line2d');
var Registry = _dereq_('../../registry');
var prepareRegl = _dereq_('../../lib/prepare_regl');
var getModuleCalcData = _dereq_('../../plots/get_data').getModuleCalcData;
var Cartesian = _dereq_('../../plots/cartesian');
var getFromId = _dereq_('../../plots/cartesian/axis_ids').getFromId;
var shouldShowZeroLine = _dereq_('../../plots/cartesian/axes').shouldShowZeroLine;
var SPLOM = 'splom';
function plot(gd) {
var fullLayout = gd._fullLayout;
var _module = Registry.getModule(SPLOM);
var splomCalcData = getModuleCalcData(gd.calcdata, _module)[0];
var success = prepareRegl(gd, ['ANGLE_instanced_arrays', 'OES_element_index_uint']);
if(!success) return;
if(fullLayout._hasOnlyLargeSploms) {
updateGrid(gd);
}
_module.plot(gd, {}, splomCalcData);
}
function drag(gd) {
var cd = gd.calcdata;
var fullLayout = gd._fullLayout;
if(fullLayout._hasOnlyLargeSploms) {
updateGrid(gd);
}
for(var i = 0; i < cd.length; i++) {
var cd0 = cd[i][0];
var trace = cd0.trace;
var scene = fullLayout._splomScenes[trace.uid];
if(trace.type === 'splom' && scene && scene.matrix) {
dragOne(gd, trace, scene);
}
}
}
function dragOne(gd, trace, scene) {
var visibleLength = scene.matrixOptions.data.length;
var visibleDims = trace._visibleDims;
var ranges = scene.viewOpts.ranges = new Array(visibleLength);
for(var k = 0; k < visibleDims.length; k++) {
var i = visibleDims[k];
var rng = ranges[k] = new Array(4);
var xa = getFromId(gd, trace._diag[i][0]);
if(xa) {
rng[0] = xa.r2l(xa.range[0]);
rng[2] = xa.r2l(xa.range[1]);
}
var ya = getFromId(gd, trace._diag[i][1]);
if(ya) {
rng[1] = ya.r2l(ya.range[0]);
rng[3] = ya.r2l(ya.range[1]);
}
}
if(scene.selectBatch.length || scene.unselectBatch.length) {
scene.matrix.update({ranges: ranges}, {ranges: ranges});
} else {
scene.matrix.update({ranges: ranges});
}
}
function updateGrid(gd) {
var fullLayout = gd._fullLayout;
var regl = fullLayout._glcanvas.data()[0].regl;
var splomGrid = fullLayout._splomGrid;
if(!splomGrid) {
splomGrid = fullLayout._splomGrid = createLine(regl);
}
splomGrid.update(makeGridData(gd));
}
function makeGridData(gd) {
var plotGlPixelRatio = gd._context.plotGlPixelRatio;
var fullLayout = gd._fullLayout;
var gs = fullLayout._size;
var fullView = [
0, 0,
fullLayout.width * plotGlPixelRatio,
fullLayout.height * plotGlPixelRatio
];
var lookup = {};
var k;
function push(prefix, ax, x0, x1, y0, y1) {
x0 *= plotGlPixelRatio;
x1 *= plotGlPixelRatio;
y0 *= plotGlPixelRatio;
y1 *= plotGlPixelRatio;
var lcolor = ax[prefix + 'color'];
var lwidth = ax[prefix + 'width'];
var key = String(lcolor + lwidth);
if(key in lookup) {
lookup[key].data.push(NaN, NaN, x0, x1, y0, y1);
} else {
lookup[key] = {
data: [x0, x1, y0, y1],
join: 'rect',
thickness: lwidth * plotGlPixelRatio,
color: lcolor,
viewport: fullView,
range: fullView,
overlay: false
};
}
}
for(k in fullLayout._splomSubplots) {
var sp = fullLayout._plots[k];
var xa = sp.xaxis;
var ya = sp.yaxis;
var xVals = xa._gridVals;
var yVals = ya._gridVals;
var xOffset = xa._offset;
var xLength = xa._length;
var yLength = ya._length;
// ya.l2p assumes top-to-bottom coordinate system (a la SVG),
// we need to compute bottom-to-top offsets and slopes:
var yOffset = gs.b + ya.domain[0] * gs.h;
var ym = -ya._m;
var yb = -ym * ya.r2l(ya.range[0], ya.calendar);
var x, y;
if(xa.showgrid) {
for(k = 0; k < xVals.length; k++) {
x = xOffset + xa.l2p(xVals[k].x);
push('grid', xa, x, yOffset, x, yOffset + yLength);
}
}
if(ya.showgrid) {
for(k = 0; k < yVals.length; k++) {
y = yOffset + yb + ym * yVals[k].x;
push('grid', ya, xOffset, y, xOffset + xLength, y);
}
}
if(shouldShowZeroLine(gd, xa, ya)) {
x = xOffset + xa.l2p(0);
push('zeroline', xa, x, yOffset, x, yOffset + yLength);
}
if(shouldShowZeroLine(gd, ya, xa)) {
y = yOffset + yb + 0;
push('zeroline', ya, xOffset, y, xOffset + xLength, y);
}
}
var gridBatches = [];
for(k in lookup) {
gridBatches.push(lookup[k]);
}
return gridBatches;
}
function clean(newFullData, newFullLayout, oldFullData, oldFullLayout) {
var lookup = {};
var i;
if(oldFullLayout._splomScenes) {
for(i = 0; i < newFullData.length; i++) {
var newTrace = newFullData[i];
if(newTrace.type === 'splom') {
lookup[newTrace.uid] = 1;
}
}
for(i = 0; i < oldFullData.length; i++) {
var oldTrace = oldFullData[i];
if(!lookup[oldTrace.uid]) {
var scene = oldFullLayout._splomScenes[oldTrace.uid];
if(scene && scene.destroy) scene.destroy();
// must first set scene to null in order to get garbage collected
oldFullLayout._splomScenes[oldTrace.uid] = null;
delete oldFullLayout._splomScenes[oldTrace.uid];
}
}
}
if(Object.keys(oldFullLayout._splomScenes || {}).length === 0) {
delete oldFullLayout._splomScenes;
}
if(oldFullLayout._splomGrid &&
(!newFullLayout._hasOnlyLargeSploms && oldFullLayout._hasOnlyLargeSploms)) {
// must first set scene to null in order to get garbage collected
oldFullLayout._splomGrid.destroy();
oldFullLayout._splomGrid = null;
delete oldFullLayout._splomGrid;
}
Cartesian.clean(newFullData, newFullLayout, oldFullData, oldFullLayout);
}
module.exports = {
name: SPLOM,
attr: Cartesian.attr,
attrRegex: Cartesian.attrRegex,
layoutAttributes: Cartesian.layoutAttributes,
supplyLayoutDefaults: Cartesian.supplyLayoutDefaults,
drawFramework: Cartesian.drawFramework,
plot: plot,
drag: drag,
updateGrid: updateGrid,
clean: clean,
updateFx: Cartesian.updateFx,
toSVG: Cartesian.toSVG
};
},{"../../lib/prepare_regl":516,"../../plots/cartesian":568,"../../plots/cartesian/axes":554,"../../plots/cartesian/axis_ids":558,"../../plots/get_data":593,"../../registry":638,"regl-line2d":280}],1030:[function(_dereq_,module,exports){
'use strict';
var Lib = _dereq_('../../lib');
var AxisIDs = _dereq_('../../plots/cartesian/axis_ids');
var calcMarkerSize = _dereq_('../scatter/calc').calcMarkerSize;
var calcAxisExpansion = _dereq_('../scatter/calc').calcAxisExpansion;
var calcColorscale = _dereq_('../scatter/colorscale_calc');
var convertMarkerSelection = _dereq_('../scattergl/convert').markerSelection;
var convertMarkerStyle = _dereq_('../scattergl/convert').markerStyle;
var sceneUpdate = _dereq_('./scene_update');
var BADNUM = _dereq_('../../constants/numerical').BADNUM;
var TOO_MANY_POINTS = _dereq_('../scattergl/constants').TOO_MANY_POINTS;
module.exports = function calc(gd, trace) {
var dimensions = trace.dimensions;
var commonLength = trace._length;
var opts = {};
// 'c' for calculated, 'l' for linear,
// only differ here for log axes, pass ldata to createMatrix as 'data'
var cdata = opts.cdata = [];
var ldata = opts.data = [];
// keep track of visible dimensions
var visibleDims = trace._visibleDims = [];
var i, k, dim, xa, ya;
function makeCalcdata(ax, dim) {
// call makeCalcdata with fake input
var ccol = ax.makeCalcdata({
v: dim.values,
vcalendar: trace.calendar
}, 'v');
for(var j = 0; j < ccol.length; j++) {
ccol[j] = ccol[j] === BADNUM ? NaN : ccol[j];
}
cdata.push(ccol);
ldata.push(ax.type === 'log' ? Lib.simpleMap(ccol, ax.c2l) : ccol);
}
for(i = 0; i < dimensions.length; i++) {
dim = dimensions[i];
if(dim.visible) {
xa = AxisIDs.getFromId(gd, trace._diag[i][0]);
ya = AxisIDs.getFromId(gd, trace._diag[i][1]);
// if corresponding x & y axes don't have matching types, skip dim
if(xa && ya && xa.type !== ya.type) {
Lib.log('Skipping splom dimension ' + i + ' with conflicting axis types');
continue;
}
if(xa) {
makeCalcdata(xa, dim);
if(ya && ya.type === 'category') {
ya._categories = xa._categories.slice();
}
} else {
// should not make it here, if both xa and ya undefined
makeCalcdata(ya, dim);
}
visibleDims.push(i);
}
}
calcColorscale(gd, trace);
Lib.extendFlat(opts, convertMarkerStyle(trace));
var visibleLength = cdata.length;
var hasTooManyPoints = (visibleLength * commonLength) > TOO_MANY_POINTS;
// Reuse SVG scatter axis expansion routine.
// For graphs with very large number of points and array marker.size,
// use average marker size instead to speed things up.
var ppad;
if(hasTooManyPoints) {
ppad = opts.sizeAvg || Math.max(opts.size, 3);
} else {
ppad = calcMarkerSize(trace, commonLength);
}
for(k = 0; k < visibleDims.length; k++) {
i = visibleDims[k];
dim = dimensions[i];
xa = AxisIDs.getFromId(gd, trace._diag[i][0]) || {};
ya = AxisIDs.getFromId(gd, trace._diag[i][1]) || {};
calcAxisExpansion(gd, trace, xa, ya, cdata[k], cdata[k], ppad);
}
var scene = sceneUpdate(gd, trace);
if(!scene.matrix) scene.matrix = true;
scene.matrixOptions = opts;
scene.selectedOptions = convertMarkerSelection(trace, trace.selected);
scene.unselectedOptions = convertMarkerSelection(trace, trace.unselected);
return [{x: false, y: false, t: {}, trace: trace}];
};
},{"../../constants/numerical":479,"../../lib":503,"../../plots/cartesian/axis_ids":558,"../scatter/calc":926,"../scatter/colorscale_calc":928,"../scattergl/constants":979,"../scattergl/convert":980,"./scene_update":1037}],1031:[function(_dereq_,module,exports){
'use strict';
var Lib = _dereq_('../../lib');
var handleArrayContainerDefaults = _dereq_('../../plots/array_container_defaults');
var attributes = _dereq_('./attributes');
var subTypes = _dereq_('../scatter/subtypes');
var handleMarkerDefaults = _dereq_('../scatter/marker_defaults');
var mergeLength = _dereq_('../parcoords/merge_length');
var isOpenSymbol = _dereq_('../scattergl/helpers').isOpenSymbol;
module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout) {
function coerce(attr, dflt) {
return Lib.coerce(traceIn, traceOut, attributes, attr, dflt);
}
var dimensions = handleArrayContainerDefaults(traceIn, traceOut, {
name: 'dimensions',
handleItemDefaults: dimensionDefaults
});
var showDiag = coerce('diagonal.visible');
var showUpper = coerce('showupperhalf');
var showLower = coerce('showlowerhalf');
var dimLength = mergeLength(traceOut, dimensions, 'values');
if(!dimLength || (!showDiag && !showUpper && !showLower)) {
traceOut.visible = false;
return;
}
coerce('text');
coerce('hovertext');
coerce('hovertemplate');
coerce('xhoverformat');
coerce('yhoverformat');
handleMarkerDefaults(traceIn, traceOut, defaultColor, layout, coerce);
var isOpen = isOpenSymbol(traceOut.marker.symbol);
var isBubble = subTypes.isBubble(traceOut);
coerce('marker.line.width', isOpen || isBubble ? 1 : 0);
handleAxisDefaults(traceIn, traceOut, layout, coerce);
Lib.coerceSelectionMarkerOpacity(traceOut, coerce);
};
function dimensionDefaults(dimIn, dimOut) {
function coerce(attr, dflt) {
return Lib.coerce(dimIn, dimOut, attributes.dimensions, attr, dflt);
}
coerce('label');
var values = coerce('values');
if(!(values && values.length)) dimOut.visible = false;
else coerce('visible');
coerce('axis.type');
coerce('axis.matches');
}
function handleAxisDefaults(traceIn, traceOut, layout, coerce) {
var dimensions = traceOut.dimensions;
var dimLength = dimensions.length;
var showUpper = traceOut.showupperhalf;
var showLower = traceOut.showlowerhalf;
var showDiag = traceOut.diagonal.visible;
var i, j;
var xAxesDflt = new Array(dimLength);
var yAxesDflt = new Array(dimLength);
for(i = 0; i < dimLength; i++) {
var suffix = i ? i + 1 : '';
xAxesDflt[i] = 'x' + suffix;
yAxesDflt[i] = 'y' + suffix;
}
var xaxes = coerce('xaxes', xAxesDflt);
var yaxes = coerce('yaxes', yAxesDflt);
// build list of [x,y] axis corresponding to each dimensions[i],
// very useful for passing options to regl-splom
var diag = traceOut._diag = new Array(dimLength);
// lookup for 'drawn' x|y axes, to avoid costly indexOf downstream
traceOut._xaxes = {};
traceOut._yaxes = {};
// list of 'drawn' x|y axes, use to generate list of subplots
var xList = [];
var yList = [];
function fillAxisStashes(axId, counterAxId, dim, list) {
if(!axId) return;
var axLetter = axId.charAt(0);
var stash = layout._splomAxes[axLetter];
traceOut['_' + axLetter + 'axes'][axId] = 1;
list.push(axId);
if(!(axId in stash)) {
var s = stash[axId] = {};
if(dim) {
s.label = dim.label || '';
if(dim.visible && dim.axis) {
if(dim.axis.type) s.type = dim.axis.type;
if(dim.axis.matches) s.matches = counterAxId;
}
}
}
}
// cases where showDiag and showLower or showUpper are false
// no special treatment as the 'drawn' x-axes and y-axes no longer match
// the dimensions items and xaxes|yaxes 1-to-1
var mustShiftX = !showDiag && !showLower;
var mustShiftY = !showDiag && !showUpper;
traceOut._axesDim = {};
for(i = 0; i < dimLength; i++) {
var dim = dimensions[i];
var i0 = i === 0;
var iN = i === dimLength - 1;
var xaId = (i0 && mustShiftX) || (iN && mustShiftY) ?
undefined :
xaxes[i];
var yaId = (i0 && mustShiftY) || (iN && mustShiftX) ?
undefined :
yaxes[i];
fillAxisStashes(xaId, yaId, dim, xList);
fillAxisStashes(yaId, xaId, dim, yList);
diag[i] = [xaId, yaId];
traceOut._axesDim[xaId] = i;
traceOut._axesDim[yaId] = i;
}
// fill in splom subplot keys
for(i = 0; i < xList.length; i++) {
for(j = 0; j < yList.length; j++) {
var id = xList[i] + yList[j];
if(i > j && showUpper) {
layout._splomSubplots[id] = 1;
} else if(i < j && showLower) {
layout._splomSubplots[id] = 1;
} else if(i === j && (showDiag || !showLower || !showUpper)) {
// need to include diagonal subplots when
// hiding one half and the diagonal
layout._splomSubplots[id] = 1;
}
}
}
// when lower half is omitted, or when just the diagonal is gone,
// override grid default to make sure axes remain on
// the left/bottom of the plot area
if(!showLower || (!showDiag && showUpper && showLower)) {
layout._splomGridDflt.xside = 'bottom';
layout._splomGridDflt.yside = 'left';
}
}
},{"../../lib":503,"../../plots/array_container_defaults":549,"../parcoords/merge_length":896,"../scatter/marker_defaults":944,"../scatter/subtypes":950,"../scattergl/helpers":984,"./attributes":1028}],1032:[function(_dereq_,module,exports){
'use strict';
var Lib = _dereq_('../../lib');
var calcColorscale = _dereq_('../scatter/colorscale_calc');
var convertMarkerStyle = _dereq_('../scattergl/convert').markerStyle;
module.exports = function editStyle(gd, cd0) {
var trace = cd0.trace;
var scene = gd._fullLayout._splomScenes[trace.uid];
if(scene) {
calcColorscale(gd, trace);
Lib.extendFlat(scene.matrixOptions, convertMarkerStyle(trace));
// TODO [un]selected styles?
var opts = Lib.extendFlat({}, scene.matrixOptions, scene.viewOpts);
// TODO this is too long for arrayOk attributes!
scene.matrix.update(opts, null);
}
};
},{"../../lib":503,"../scatter/colorscale_calc":928,"../scattergl/convert":980}],1033:[function(_dereq_,module,exports){
'use strict';
exports.getDimIndex = function getDimIndex(trace, ax) {
var axId = ax._id;
var axLetter = axId.charAt(0);
var ind = {x: 0, y: 1}[axLetter];
var visibleDims = trace._visibleDims;
for(var k = 0; k < visibleDims.length; k++) {
var i = visibleDims[k];
if(trace._diag[i][ind] === axId) return k;
}
return false;
};
},{}],1034:[function(_dereq_,module,exports){
'use strict';
var helpers = _dereq_('./helpers');
var calcHover = _dereq_('../scattergl/hover').calcHover;
function hoverPoints(pointData, xval, yval) {
var cd = pointData.cd;
var trace = cd[0].trace;
var scene = pointData.scene;
var cdata = scene.matrixOptions.cdata;
var xa = pointData.xa;
var ya = pointData.ya;
var xpx = xa.c2p(xval);
var ypx = ya.c2p(yval);
var maxDistance = pointData.distance;
var xi = helpers.getDimIndex(trace, xa);
var yi = helpers.getDimIndex(trace, ya);
if(xi === false || yi === false) return [pointData];
var x = cdata[xi];
var y = cdata[yi];
var id, dxy;
var minDist = maxDistance;
for(var i = 0; i < x.length; i++) {
var ptx = x[i];
var pty = y[i];
var dx = xa.c2p(ptx) - xpx;
var dy = ya.c2p(pty) - ypx;
var dist = Math.sqrt(dx * dx + dy * dy);
if(dist < minDist) {
minDist = dxy = dist;
id = i;
}
}
pointData.index = id;
pointData.distance = minDist;
pointData.dxy = dxy;
if(id === undefined) return [pointData];
return [calcHover(pointData, x, y, trace)];
}
module.exports = {
hoverPoints: hoverPoints
};
},{"../scattergl/hover":985,"./helpers":1033}],1035:[function(_dereq_,module,exports){
'use strict';
var Registry = _dereq_('../../registry');
var Grid = _dereq_('../../components/grid');
module.exports = {
moduleType: 'trace',
name: 'splom',
basePlotModule: _dereq_('./base_plot'),
categories: ['gl', 'regl', 'cartesian', 'symbols', 'showLegend', 'scatter-like'],
attributes: _dereq_('./attributes'),
supplyDefaults: _dereq_('./defaults'),
colorbar: _dereq_('../scatter/marker_colorbar'),
calc: _dereq_('./calc'),
plot: _dereq_('./plot'),
hoverPoints: _dereq_('./hover').hoverPoints,
selectPoints: _dereq_('./select'),
editStyle: _dereq_('./edit_style'),
meta: {
}
};
// splom traces use the 'grid' component to generate their axes,
// register it here
Registry.register(Grid);
},{"../../components/grid":410,"../../registry":638,"../scatter/marker_colorbar":943,"./attributes":1028,"./base_plot":1029,"./calc":1030,"./defaults":1031,"./edit_style":1032,"./hover":1034,"./plot":1036,"./select":1038}],1036:[function(_dereq_,module,exports){
'use strict';
var createMatrix = _dereq_('regl-splom');
var Lib = _dereq_('../../lib');
var AxisIDs = _dereq_('../../plots/cartesian/axis_ids');
var selectMode = _dereq_('../../components/dragelement/helpers').selectMode;
module.exports = function plot(gd, _, splomCalcData) {
if(!splomCalcData.length) return;
for(var i = 0; i < splomCalcData.length; i++) {
plotOne(gd, splomCalcData[i][0]);
}
};
function plotOne(gd, cd0) {
var fullLayout = gd._fullLayout;
var gs = fullLayout._size;
var trace = cd0.trace;
var stash = cd0.t;
var scene = fullLayout._splomScenes[trace.uid];
var matrixOpts = scene.matrixOptions;
var cdata = matrixOpts.cdata;
var regl = fullLayout._glcanvas.data()[0].regl;
var dragmode = fullLayout.dragmode;
var xa, ya;
var i, j, k;
if(cdata.length === 0) return;
// augment options with proper upper/lower halves
// regl-splom's default grid starts from bottom-left
matrixOpts.lower = trace.showupperhalf;
matrixOpts.upper = trace.showlowerhalf;
matrixOpts.diagonal = trace.diagonal.visible;
var visibleDims = trace._visibleDims;
var visibleLength = cdata.length;
var viewOpts = scene.viewOpts = {};
viewOpts.ranges = new Array(visibleLength);
viewOpts.domains = new Array(visibleLength);
for(k = 0; k < visibleDims.length; k++) {
i = visibleDims[k];
var rng = viewOpts.ranges[k] = new Array(4);
var dmn = viewOpts.domains[k] = new Array(4);
xa = AxisIDs.getFromId(gd, trace._diag[i][0]);
if(xa) {
rng[0] = xa._rl[0];
rng[2] = xa._rl[1];
dmn[0] = xa.domain[0];
dmn[2] = xa.domain[1];
}
ya = AxisIDs.getFromId(gd, trace._diag[i][1]);
if(ya) {
rng[1] = ya._rl[0];
rng[3] = ya._rl[1];
dmn[1] = ya.domain[0];
dmn[3] = ya.domain[1];
}
}
var plotGlPixelRatio = gd._context.plotGlPixelRatio;
var l = gs.l * plotGlPixelRatio;
var b = gs.b * plotGlPixelRatio;
var w = gs.w * plotGlPixelRatio;
var h = gs.h * plotGlPixelRatio;
viewOpts.viewport = [l, b, w + l, h + b];
if(scene.matrix === true) {
scene.matrix = createMatrix(regl);
}
var clickSelectEnabled = fullLayout.clickmode.indexOf('select') > -1;
var isSelectMode = selectMode(dragmode) ||
!!trace.selectedpoints || clickSelectEnabled;
var needsBaseUpdate = true;
if(isSelectMode) {
var commonLength = trace._length;
// regenerate scene batch, if traces number changed during selection
if(trace.selectedpoints) {
scene.selectBatch = trace.selectedpoints;
var selPts = trace.selectedpoints;
var selDict = {};
for(i = 0; i < selPts.length; i++) {
selDict[selPts[i]] = true;
}
var unselPts = [];
for(i = 0; i < commonLength; i++) {
if(!selDict[i]) unselPts.push(i);
}
scene.unselectBatch = unselPts;
}
// precalculate px coords since we are not going to pan during select
var xpx = stash.xpx = new Array(visibleLength);
var ypx = stash.ypx = new Array(visibleLength);
for(k = 0; k < visibleDims.length; k++) {
i = visibleDims[k];
xa = AxisIDs.getFromId(gd, trace._diag[i][0]);
if(xa) {
xpx[k] = new Array(commonLength);
for(j = 0; j < commonLength; j++) {
xpx[k][j] = xa.c2p(cdata[k][j]);
}
}
ya = AxisIDs.getFromId(gd, trace._diag[i][1]);
if(ya) {
ypx[k] = new Array(commonLength);
for(j = 0; j < commonLength; j++) {
ypx[k][j] = ya.c2p(cdata[k][j]);
}
}
}
if(scene.selectBatch.length || scene.unselectBatch.length) {
var unselOpts = Lib.extendFlat({}, matrixOpts, scene.unselectedOptions, viewOpts);
var selOpts = Lib.extendFlat({}, matrixOpts, scene.selectedOptions, viewOpts);
scene.matrix.update(unselOpts, selOpts);
needsBaseUpdate = false;
}
} else {
stash.xpx = stash.ypx = null;
}
if(needsBaseUpdate) {
var opts = Lib.extendFlat({}, matrixOpts, viewOpts);
scene.matrix.update(opts, null);
}
}
},{"../../components/dragelement/helpers":384,"../../lib":503,"../../plots/cartesian/axis_ids":558,"regl-splom":282}],1037:[function(_dereq_,module,exports){
'use strict';
var Lib = _dereq_('../../lib');
module.exports = function sceneUpdate(gd, trace) {
var fullLayout = gd._fullLayout;
var uid = trace.uid;
// must place ref to 'scene' in fullLayout, so that:
// - it can be relinked properly on updates
// - it can be destroyed properly when needed
var splomScenes = fullLayout._splomScenes;
if(!splomScenes) splomScenes = fullLayout._splomScenes = {};
var reset = {
dirty: true,
selectBatch: [],
unselectBatch: []
};
var first = {
matrix: false,
selectBatch: [],
unselectBatch: []
};
var scene = splomScenes[trace.uid];
if(!scene) {
scene = splomScenes[uid] = Lib.extendFlat({}, reset, first);
scene.draw = function draw() {
if(scene.matrix && scene.matrix.draw) {
if(scene.selectBatch.length || scene.unselectBatch.length) {
scene.matrix.draw(scene.unselectBatch, scene.selectBatch);
} else {
scene.matrix.draw();
}
}
scene.dirty = false;
};
// remove scene resources
scene.destroy = function destroy() {
if(scene.matrix && scene.matrix.destroy) {
scene.matrix.destroy();
}
scene.matrixOptions = null;
scene.selectBatch = null;
scene.unselectBatch = null;
scene = null;
};
}
// In case if we have scene from the last calc - reset data
if(!scene.dirty) {
Lib.extendFlat(scene, reset);
}
return scene;
};
},{"../../lib":503}],1038:[function(_dereq_,module,exports){
'use strict';
var Lib = _dereq_('../../lib');
var subTypes = _dereq_('../scatter/subtypes');
var helpers = _dereq_('./helpers');
module.exports = function select(searchInfo, selectionTester) {
var cd = searchInfo.cd;
var trace = cd[0].trace;
var stash = cd[0].t;
var scene = searchInfo.scene;
var cdata = scene.matrixOptions.cdata;
var xa = searchInfo.xaxis;
var ya = searchInfo.yaxis;
var selection = [];
if(!scene) return selection;
var hasOnlyLines = (!subTypes.hasMarkers(trace) && !subTypes.hasText(trace));
if(trace.visible !== true || hasOnlyLines) return selection;
var xi = helpers.getDimIndex(trace, xa);
var yi = helpers.getDimIndex(trace, ya);
if(xi === false || yi === false) return selection;
var xpx = stash.xpx[xi];
var ypx = stash.ypx[yi];
var x = cdata[xi];
var y = cdata[yi];
var els = [];
var unels = [];
// degenerate polygon does not enable selection
// filter out points by visible scatter ones
if(selectionTester !== false && !selectionTester.degenerate) {
for(var i = 0; i < x.length; i++) {
if(selectionTester.contains([xpx[i], ypx[i]], null, i, searchInfo)) {
els.push(i);
selection.push({
pointNumber: i,
x: x[i],
y: y[i]
});
} else {
unels.push(i);
}
}
}
var matrixOpts = scene.matrixOptions;
if(!els.length && !unels.length) {
scene.matrix.update(matrixOpts, null);
} else if(!scene.selectBatch.length && !scene.unselectBatch.length) {
scene.matrix.update(
scene.unselectedOptions,
Lib.extendFlat({}, matrixOpts, scene.selectedOptions, scene.viewOpts)
);
}
scene.selectBatch = els;
scene.unselectBatch = unels;
return selection;
};
},{"../../lib":503,"../scatter/subtypes":950,"./helpers":1033}],1039:[function(_dereq_,module,exports){
'use strict';
var colorScaleAttrs = _dereq_('../../components/colorscale/attributes');
var axisHoverFormat = _dereq_('../../plots/cartesian/axis_format_attributes').axisHoverFormat;
var hovertemplateAttrs = _dereq_('../../plots/template_attributes').hovertemplateAttrs;
var mesh3dAttrs = _dereq_('../mesh3d/attributes');
var baseAttrs = _dereq_('../../plots/attributes');
var extendFlat = _dereq_('../../lib/extend').extendFlat;
var attrs = {
x: {
valType: 'data_array',
editType: 'calc+clearAxisTypes',
},
y: {
valType: 'data_array',
editType: 'calc+clearAxisTypes',
},
z: {
valType: 'data_array',
editType: 'calc+clearAxisTypes',
},
u: {
valType: 'data_array',
editType: 'calc',
},
v: {
valType: 'data_array',
editType: 'calc',
},
w: {
valType: 'data_array',
editType: 'calc',
},
starts: {
x: {
valType: 'data_array',
editType: 'calc',
},
y: {
valType: 'data_array',
editType: 'calc',
},
z: {
valType: 'data_array',
editType: 'calc',
},
editType: 'calc'
},
maxdisplayed: {
valType: 'integer',
min: 0,
dflt: 1000,
editType: 'calc',
},
// TODO
//
// Should add 'absolute' (like cone traces have), but currently gl-streamtube3d's
// `absoluteTubeSize` doesn't behave well enough for our needs.
//
// 'fixed' would be a nice addition to plot stream 'lines', see
// https://github.com/plotly/plotly.js/commit/812be20750e21e0a1831975001c248d365850f73#r29129877
//
// sizemode: {
// valType: 'enumerated',
// values: ['scaled', 'absolute', 'fixed'],
// dflt: 'scaled',
// editType: 'calc',
//
// },
sizeref: {
valType: 'number',
editType: 'calc',
min: 0,
dflt: 1,
},
text: {
valType: 'string',
dflt: '',
editType: 'calc',
},
hovertext: {
valType: 'string',
dflt: '',
editType: 'calc',
},
hovertemplate: hovertemplateAttrs({editType: 'calc'}, {
keys: [
'tubex', 'tubey', 'tubez',
'tubeu', 'tubev', 'tubew',
'norm', 'divergence'
]
}),
uhoverformat: axisHoverFormat('u', 1),
vhoverformat: axisHoverFormat('v', 1),
whoverformat: axisHoverFormat('w', 1),
xhoverformat: axisHoverFormat('x'),
yhoverformat: axisHoverFormat('y'),
zhoverformat: axisHoverFormat('z'),
showlegend: extendFlat({}, baseAttrs.showlegend, {dflt: false})
};
extendFlat(attrs, colorScaleAttrs('', {
colorAttr: 'u/v/w norm',
showScaleDflt: true,
editTypeOverride: 'calc'
}));
var fromMesh3d = ['opacity', 'lightposition', 'lighting'];
fromMesh3d.forEach(function(k) {
attrs[k] = mesh3dAttrs[k];
});
attrs.hoverinfo = extendFlat({}, baseAttrs.hoverinfo, {
editType: 'calc',
flags: ['x', 'y', 'z', 'u', 'v', 'w', 'norm', 'divergence', 'text', 'name'],
dflt: 'x+y+z+norm+text+name'
});
attrs.transforms = undefined;
module.exports = attrs;
},{"../../components/colorscale/attributes":373,"../../lib/extend":493,"../../plots/attributes":550,"../../plots/cartesian/axis_format_attributes":557,"../../plots/template_attributes":633,"../mesh3d/attributes":866}],1040:[function(_dereq_,module,exports){
'use strict';
var Lib = _dereq_('../../lib');
var colorscaleCalc = _dereq_('../../components/colorscale/calc');
function calc(gd, trace) {
trace._len = Math.min(
trace.u.length,
trace.v.length,
trace.w.length,
trace.x.length,
trace.y.length,
trace.z.length
);
trace._u = filter(trace.u, trace._len);
trace._v = filter(trace.v, trace._len);
trace._w = filter(trace.w, trace._len);
trace._x = filter(trace.x, trace._len);
trace._y = filter(trace.y, trace._len);
trace._z = filter(trace.z, trace._len);
var grid = processGrid(trace);
trace._gridFill = grid.fill;
trace._Xs = grid.Xs;
trace._Ys = grid.Ys;
trace._Zs = grid.Zs;
trace._len = grid.len;
var slen = 0;
var startx, starty, startz;
if(trace.starts) {
startx = filter(trace.starts.x || []);
starty = filter(trace.starts.y || []);
startz = filter(trace.starts.z || []);
slen = Math.min(startx.length, starty.length, startz.length);
}
trace._startsX = startx || [];
trace._startsY = starty || [];
trace._startsZ = startz || [];
var normMax = 0;
var normMin = Infinity;
var i;
for(i = 0; i < trace._len; i++) {
var u = trace._u[i];
var v = trace._v[i];
var w = trace._w[i];
var norm = Math.sqrt(u * u + v * v + w * w);
normMax = Math.max(normMax, norm);
normMin = Math.min(normMin, norm);
}
colorscaleCalc(gd, trace, {
vals: [normMin, normMax],
containerStr: '',
cLetter: 'c'
});
for(i = 0; i < slen; i++) {
var sx = startx[i];
grid.xMax = Math.max(grid.xMax, sx);
grid.xMin = Math.min(grid.xMin, sx);
var sy = starty[i];
grid.yMax = Math.max(grid.yMax, sy);
grid.yMin = Math.min(grid.yMin, sy);
var sz = startz[i];
grid.zMax = Math.max(grid.zMax, sz);
grid.zMin = Math.min(grid.zMin, sz);
}
trace._slen = slen;
trace._normMax = normMax;
trace._xbnds = [grid.xMin, grid.xMax];
trace._ybnds = [grid.yMin, grid.yMax];
trace._zbnds = [grid.zMin, grid.zMax];
}
function processGrid(trace) {
var x = trace._x;
var y = trace._y;
var z = trace._z;
var len = trace._len;
var i, j, k;
var xMax = -Infinity;
var xMin = Infinity;
var yMax = -Infinity;
var yMin = Infinity;
var zMax = -Infinity;
var zMin = Infinity;
var gridFill = '';
var filledX;
var filledY;
var filledZ;
var firstX, lastX;
var firstY, lastY;
var firstZ, lastZ;
if(len) {
firstX = x[0];
firstY = y[0];
firstZ = z[0];
}
if(len > 1) {
lastX = x[len - 1];
lastY = y[len - 1];
lastZ = z[len - 1];
}
for(i = 0; i < len; i++) {
xMax = Math.max(xMax, x[i]);
xMin = Math.min(xMin, x[i]);
yMax = Math.max(yMax, y[i]);
yMin = Math.min(yMin, y[i]);
zMax = Math.max(zMax, z[i]);
zMin = Math.min(zMin, z[i]);
if(!filledX && x[i] !== firstX) {
filledX = true;
gridFill += 'x';
}
if(!filledY && y[i] !== firstY) {
filledY = true;
gridFill += 'y';
}
if(!filledZ && z[i] !== firstZ) {
filledZ = true;
gridFill += 'z';
}
}
// fill if not filled - case of having dimension(s) with one item
if(!filledX) gridFill += 'x';
if(!filledY) gridFill += 'y';
if(!filledZ) gridFill += 'z';
var Xs = distinctVals(trace._x);
var Ys = distinctVals(trace._y);
var Zs = distinctVals(trace._z);
gridFill = gridFill.replace('x', (firstX > lastX ? '-' : '+') + 'x');
gridFill = gridFill.replace('y', (firstY > lastY ? '-' : '+') + 'y');
gridFill = gridFill.replace('z', (firstZ > lastZ ? '-' : '+') + 'z');
var empty = function() {
len = 0;
Xs = [];
Ys = [];
Zs = [];
};
// Over-specified mesh case, this would error in tube2mesh
if(!len || len < Xs.length * Ys.length * Zs.length) empty();
var getArray = function(c) { return c === 'x' ? x : c === 'y' ? y : z; };
var getVals = function(c) { return c === 'x' ? Xs : c === 'y' ? Ys : Zs; };
var getDir = function(c) { return c[len - 1] < c[0] ? -1 : 1; };
var arrK = getArray(gridFill[1]);
var arrJ = getArray(gridFill[3]);
var arrI = getArray(gridFill[5]);
var nk = getVals(gridFill[1]).length;
var nj = getVals(gridFill[3]).length;
var ni = getVals(gridFill[5]).length;
var arbitrary = false;
var getIndex = function(_i, _j, _k) {
return nk * (nj * _i + _j) + _k;
};
var dirK = getDir(getArray(gridFill[1]));
var dirJ = getDir(getArray(gridFill[3]));
var dirI = getDir(getArray(gridFill[5]));
for(i = 0; i < ni - 1; i++) {
for(j = 0; j < nj - 1; j++) {
for(k = 0; k < nk - 1; k++) {
var q000 = getIndex(i, j, k);
var q001 = getIndex(i, j, k + 1);
var q010 = getIndex(i, j + 1, k);
var q100 = getIndex(i + 1, j, k);
if(
!(arrK[q000] * dirK < arrK[q001] * dirK) ||
!(arrJ[q000] * dirJ < arrJ[q010] * dirJ) ||
!(arrI[q000] * dirI < arrI[q100] * dirI)
) {
arbitrary = true;
}
if(arbitrary) break;
}
if(arbitrary) break;
}
if(arbitrary) break;
}
if(arbitrary) {
Lib.warn('Encountered arbitrary coordinates! Unable to input data grid.');
empty();
}
return {
xMin: xMin,
yMin: yMin,
zMin: zMin,
xMax: xMax,
yMax: yMax,
zMax: zMax,
Xs: Xs,
Ys: Ys,
Zs: Zs,
len: len,
fill: gridFill
};
}
function distinctVals(col) {
return Lib.distinctVals(col).vals;
}
function filter(arr, len) {
if(len === undefined) len = arr.length;
// no need for casting typed arrays to numbers
if(Lib.isTypedArray(arr)) return arr.subarray(0, len);
var values = [];
for(var i = 0; i < len; i++) {
values[i] = +arr[i];
}
return values;
}
module.exports = {
calc: calc,
filter: filter,
processGrid: processGrid
};
},{"../../components/colorscale/calc":374,"../../lib":503}],1041:[function(_dereq_,module,exports){
'use strict';
var tube2mesh = _dereq_('../../../stackgl_modules').gl_streamtube3d;
var createTubeMesh = tube2mesh.createTubeMesh;
var Lib = _dereq_('../../lib');
var parseColorScale = _dereq_('../../lib/gl_format_color').parseColorScale;
var extractOpts = _dereq_('../../components/colorscale').extractOpts;
var zip3 = _dereq_('../../plots/gl3d/zip3');
var axisName2scaleIndex = {xaxis: 0, yaxis: 1, zaxis: 2};
function Streamtube(scene, uid) {
this.scene = scene;
this.uid = uid;
this.mesh = null;
this.data = null;
}
var proto = Streamtube.prototype;
proto.handlePick = function(selection) {
var sceneLayout = this.scene.fullSceneLayout;
var dataScale = this.scene.dataScale;
function fromDataScale(v, axisName) {
var ax = sceneLayout[axisName];
var scale = dataScale[axisName2scaleIndex[axisName]];
return ax.l2c(v) / scale;
}
if(selection.object === this.mesh) {
var pos = selection.data.position;
var uvx = selection.data.velocity;
selection.traceCoordinate = [
fromDataScale(pos[0], 'xaxis'),
fromDataScale(pos[1], 'yaxis'),
fromDataScale(pos[2], 'zaxis'),
fromDataScale(uvx[0], 'xaxis'),
fromDataScale(uvx[1], 'yaxis'),
fromDataScale(uvx[2], 'zaxis'),
// u/v/w norm
selection.data.intensity * this.data._normMax,
// divergence
selection.data.divergence
];
selection.textLabel = this.data.hovertext || this.data.text;
return true;
}
};
function getDfltStartingPositions(vec) {
var len = vec.length;
var s;
if(len > 2) {
s = vec.slice(1, len - 1);
} else if(len === 2) {
s = [(vec[0] + vec[1]) / 2];
} else {
s = vec;
}
return s;
}
function getBoundPads(vec) {
var len = vec.length;
if(len === 1) {
return [0.5, 0.5];
} else {
return [vec[1] - vec[0], vec[len - 1] - vec[len - 2]];
}
}
function convert(scene, trace) {
var sceneLayout = scene.fullSceneLayout;
var dataScale = scene.dataScale;
var len = trace._len;
var tubeOpts = {};
function toDataCoords(arr, axisName) {
var ax = sceneLayout[axisName];
var scale = dataScale[axisName2scaleIndex[axisName]];
return Lib.simpleMap(arr, function(v) { return ax.d2l(v) * scale; });
}
tubeOpts.vectors = zip3(
toDataCoords(trace._u, 'xaxis'),
toDataCoords(trace._v, 'yaxis'),
toDataCoords(trace._w, 'zaxis'),
len
);
// Over-specified mesh case, this would error in tube2mesh
if(!len) {
return {
positions: [],
cells: []
};
}
var meshx = toDataCoords(trace._Xs, 'xaxis');
var meshy = toDataCoords(trace._Ys, 'yaxis');
var meshz = toDataCoords(trace._Zs, 'zaxis');
tubeOpts.meshgrid = [meshx, meshy, meshz];
tubeOpts.gridFill = trace._gridFill;
var slen = trace._slen;
if(slen) {
tubeOpts.startingPositions = zip3(
toDataCoords(trace._startsX, 'xaxis'),
toDataCoords(trace._startsY, 'yaxis'),
toDataCoords(trace._startsZ, 'zaxis')
);
} else {
// Default starting positions:
//
// if len>2, cut xz plane at min-y,
// takes all x/y/z pts on that plane except those on the edges
// to generate "well-defined" tubes,
//
// if len=2, take position halfway between two the pts,
//
// if len=1, take that pt
var sy0 = meshy[0];
var sx = getDfltStartingPositions(meshx);
var sz = getDfltStartingPositions(meshz);
var startingPositions = new Array(sx.length * sz.length);
var m = 0;
for(var i = 0; i < sx.length; i++) {
for(var k = 0; k < sz.length; k++) {
startingPositions[m++] = [sx[i], sy0, sz[k]];
}
}
tubeOpts.startingPositions = startingPositions;
}
tubeOpts.colormap = parseColorScale(trace);
tubeOpts.tubeSize = trace.sizeref;
tubeOpts.maxLength = trace.maxdisplayed;
// add some padding around the bounds
// to e.g. allow tubes starting from a slice of the x/y/z mesh
// to go beyond bounds a little bit w/o getting clipped
var xbnds = toDataCoords(trace._xbnds, 'xaxis');
var ybnds = toDataCoords(trace._ybnds, 'yaxis');
var zbnds = toDataCoords(trace._zbnds, 'zaxis');
var xpads = getBoundPads(meshx);
var ypads = getBoundPads(meshy);
var zpads = getBoundPads(meshz);
var bounds = [
[xbnds[0] - xpads[0], ybnds[0] - ypads[0], zbnds[0] - zpads[0]],
[xbnds[1] + xpads[1], ybnds[1] + ypads[1], zbnds[1] + zpads[1]]
];
var meshData = tube2mesh(tubeOpts, bounds);
// N.B. cmin/cmax correspond to the min/max vector norm
// in the u/v/w arrays, which in general is NOT equal to max
// intensity that colors the tubes.
var cOpts = extractOpts(trace);
meshData.vertexIntensityBounds = [cOpts.min / trace._normMax, cOpts.max / trace._normMax];
// pass gl-mesh3d lighting attributes
var lp = trace.lightposition;
meshData.lightPosition = [lp.x, lp.y, lp.z];
meshData.ambient = trace.lighting.ambient;
meshData.diffuse = trace.lighting.diffuse;
meshData.specular = trace.lighting.specular;
meshData.roughness = trace.lighting.roughness;
meshData.fresnel = trace.lighting.fresnel;
meshData.opacity = trace.opacity;
// stash autorange pad value
trace._pad = meshData.tubeScale * trace.sizeref * 2;
return meshData;
}
proto.update = function(data) {
this.data = data;
var meshData = convert(this.scene, data);
this.mesh.update(meshData);
};
proto.dispose = function() {
this.scene.glplot.remove(this.mesh);
this.mesh.dispose();
};
function createStreamtubeTrace(scene, data) {
var gl = scene.glplot.gl;
var meshData = convert(scene, data);
var mesh = createTubeMesh(gl, meshData);
var streamtube = new Streamtube(scene, data.uid);
streamtube.mesh = mesh;
streamtube.data = data;
mesh._trace = streamtube;
scene.glplot.add(mesh);
return streamtube;
}
module.exports = createStreamtubeTrace;
},{"../../../stackgl_modules":1119,"../../components/colorscale":378,"../../lib":503,"../../lib/gl_format_color":499,"../../plots/gl3d/zip3":609}],1042:[function(_dereq_,module,exports){
'use strict';
var Lib = _dereq_('../../lib');
var colorscaleDefaults = _dereq_('../../components/colorscale/defaults');
var attributes = _dereq_('./attributes');
module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout) {
function coerce(attr, dflt) {
return Lib.coerce(traceIn, traceOut, attributes, attr, dflt);
}
var u = coerce('u');
var v = coerce('v');
var w = coerce('w');
var x = coerce('x');
var y = coerce('y');
var z = coerce('z');
if(
!u || !u.length || !v || !v.length || !w || !w.length ||
!x || !x.length || !y || !y.length || !z || !z.length
) {
traceOut.visible = false;
return;
}
coerce('starts.x');
coerce('starts.y');
coerce('starts.z');
coerce('maxdisplayed');
coerce('sizeref');
coerce('lighting.ambient');
coerce('lighting.diffuse');
coerce('lighting.specular');
coerce('lighting.roughness');
coerce('lighting.fresnel');
coerce('lightposition.x');
coerce('lightposition.y');
coerce('lightposition.z');
colorscaleDefaults(traceIn, traceOut, layout, coerce, {prefix: '', cLetter: 'c'});
coerce('text');
coerce('hovertext');
coerce('hovertemplate');
coerce('uhoverformat');
coerce('vhoverformat');
coerce('whoverformat');
coerce('xhoverformat');
coerce('yhoverformat');
coerce('zhoverformat');
// disable 1D transforms (for now)
// x/y/z and u/v/w have matching lengths,
// but they don't have to match with starts.(x|y|z)
traceOut._length = null;
};
},{"../../components/colorscale/defaults":376,"../../lib":503,"./attributes":1039}],1043:[function(_dereq_,module,exports){
'use strict';
module.exports = {
moduleType: 'trace',
name: 'streamtube',
basePlotModule: _dereq_('../../plots/gl3d'),
categories: ['gl3d', 'showLegend'],
attributes: _dereq_('./attributes'),
supplyDefaults: _dereq_('./defaults'),
colorbar: {
min: 'cmin',
max: 'cmax'
},
calc: _dereq_('./calc').calc,
plot: _dereq_('./convert'),
eventData: function(out, pt) {
out.tubex = out.x;
out.tubey = out.y;
out.tubez = out.z;
out.tubeu = pt.traceCoordinate[3];
out.tubev = pt.traceCoordinate[4];
out.tubew = pt.traceCoordinate[5];
out.norm = pt.traceCoordinate[6];
out.divergence = pt.traceCoordinate[7];
// Does not correspond to input x/y/z, so delete them
delete out.x;
delete out.y;
delete out.z;
return out;
},
meta: {
}
};
},{"../../plots/gl3d":598,"./attributes":1039,"./calc":1040,"./convert":1041,"./defaults":1042}],1044:[function(_dereq_,module,exports){
'use strict';
var baseAttrs = _dereq_('../../plots/attributes');
var hovertemplateAttrs = _dereq_('../../plots/template_attributes').hovertemplateAttrs;
var texttemplateAttrs = _dereq_('../../plots/template_attributes').texttemplateAttrs;
var colorScaleAttrs = _dereq_('../../components/colorscale/attributes');
var domainAttrs = _dereq_('../../plots/domain').attributes;
var pieAttrs = _dereq_('../pie/attributes');
var constants = _dereq_('./constants');
var extendFlat = _dereq_('../../lib/extend').extendFlat;
module.exports = {
labels: {
valType: 'data_array',
editType: 'calc',
},
parents: {
valType: 'data_array',
editType: 'calc',
},
values: {
valType: 'data_array',
editType: 'calc',
},
branchvalues: {
valType: 'enumerated',
values: ['remainder', 'total'],
dflt: 'remainder',
editType: 'calc',
},
count: {
valType: 'flaglist',
flags: [
'branches',
'leaves'
],
dflt: 'leaves',
editType: 'calc',
},
level: {
valType: 'any',
editType: 'plot',
anim: true,
},
maxdepth: {
valType: 'integer',
editType: 'plot',
dflt: -1,
},
marker: extendFlat({
colors: {
valType: 'data_array',
editType: 'calc',
},
// colorinheritance: {
// valType: 'enumerated',
// values: ['per-branch', 'per-label', false]
// },
line: {
color: extendFlat({}, pieAttrs.marker.line.color, {
dflt: null,
}),
width: extendFlat({}, pieAttrs.marker.line.width, {dflt: 1}),
editType: 'calc'
},
editType: 'calc'
},
colorScaleAttrs('marker', {
colorAttr: 'colors',
anim: false // TODO: set to anim: true?
})
),
leaf: {
opacity: {
valType: 'number',
editType: 'style',
min: 0,
max: 1,
},
editType: 'plot'
},
text: pieAttrs.text,
textinfo: {
valType: 'flaglist',
flags: [
'label',
'text',
'value',
'current path',
'percent root',
'percent entry',
'percent parent'
],
extras: ['none'],
editType: 'plot',
},
// TODO: incorporate `label` and `value` in the eventData
texttemplate: texttemplateAttrs({editType: 'plot'}, {
keys: constants.eventDataKeys.concat(['label', 'value'])
}),
hovertext: pieAttrs.hovertext,
hoverinfo: extendFlat({}, baseAttrs.hoverinfo, {
flags: [
'label',
'text',
'value',
'name',
'current path',
'percent root',
'percent entry',
'percent parent'
],
dflt: 'label+text+value+name'
}),
hovertemplate: hovertemplateAttrs({}, {
keys: constants.eventDataKeys
}),
textfont: pieAttrs.textfont,
insidetextorientation: pieAttrs.insidetextorientation,
insidetextfont: pieAttrs.insidetextfont,
outsidetextfont: extendFlat({}, pieAttrs.outsidetextfont, {
}),
rotation: {
valType: 'angle',
dflt: 0,
editType: 'plot',
},
sort: pieAttrs.sort,
root: {
color: {
valType: 'color',
editType: 'calc',
dflt: 'rgba(0,0,0,0)',
},
editType: 'calc'
},
domain: domainAttrs({name: 'sunburst', trace: true, editType: 'calc'})
};
},{"../../components/colorscale/attributes":373,"../../lib/extend":493,"../../plots/attributes":550,"../../plots/domain":584,"../../plots/template_attributes":633,"../pie/attributes":899,"./constants":1047}],1045:[function(_dereq_,module,exports){
'use strict';
var plots = _dereq_('../../plots/plots');
exports.name = 'sunburst';
exports.plot = function(gd, traces, transitionOpts, makeOnCompleteCallback) {
plots.plotBasePlot(exports.name, gd, traces, transitionOpts, makeOnCompleteCallback);
};
exports.clean = function(newFullData, newFullLayout, oldFullData, oldFullLayout) {
plots.cleanBasePlot(exports.name, newFullData, newFullLayout, oldFullData, oldFullLayout);
};
},{"../../plots/plots":619}],1046:[function(_dereq_,module,exports){
'use strict';
var d3Hierarchy = _dereq_('d3-hierarchy');
var isNumeric = _dereq_('fast-isnumeric');
var Lib = _dereq_('../../lib');
var makeColorScaleFn = _dereq_('../../components/colorscale').makeColorScaleFuncFromTrace;
var makePullColorFn = _dereq_('../pie/calc').makePullColorFn;
var generateExtendedColors = _dereq_('../pie/calc').generateExtendedColors;
var colorscaleCalc = _dereq_('../../components/colorscale').calc;
var ALMOST_EQUAL = _dereq_('../../constants/numerical').ALMOST_EQUAL;
var sunburstExtendedColorWays = {};
var treemapExtendedColorWays = {};
var icicleExtendedColorWays = {};
exports.calc = function(gd, trace) {
var fullLayout = gd._fullLayout;
var ids = trace.ids;
var hasIds = Lib.isArrayOrTypedArray(ids);
var labels = trace.labels;
var parents = trace.parents;
var values = trace.values;
var hasValues = Lib.isArrayOrTypedArray(values);
var cd = [];
var parent2children = {};
var refs = {};
var addToLookup = function(parent, v) {
if(parent2children[parent]) parent2children[parent].push(v);
else parent2children[parent] = [v];
refs[v] = 1;
};
// treat number `0` as valid
var isValidKey = function(k) {
return k || typeof k === 'number';
};
var isValidVal = function(i) {
return !hasValues || (isNumeric(values[i]) && values[i] >= 0);
};
var len;
var isValid;
var getId;
if(hasIds) {
len = Math.min(ids.length, parents.length);
isValid = function(i) { return isValidKey(ids[i]) && isValidVal(i); };
getId = function(i) { return String(ids[i]); };
} else {
len = Math.min(labels.length, parents.length);
isValid = function(i) { return isValidKey(labels[i]) && isValidVal(i); };
// TODO We could allow some label / parent duplication
//
// From AJ:
// It would work OK for one level
// (multiple rows with the same name and different parents -
// or even the same parent) but if that name is then used as a parent
// which one is it?
getId = function(i) { return String(labels[i]); };
}
if(hasValues) len = Math.min(len, values.length);
for(var i = 0; i < len; i++) {
if(isValid(i)) {
var id = getId(i);
var pid = isValidKey(parents[i]) ? String(parents[i]) : '';
var cdi = {
i: i,
id: id,
pid: pid,
label: isValidKey(labels[i]) ? String(labels[i]) : ''
};
if(hasValues) cdi.v = +values[i];
cd.push(cdi);
addToLookup(pid, id);
}
}
if(!parent2children['']) {
var impliedRoots = [];
var k;
for(k in parent2children) {
if(!refs[k]) {
impliedRoots.push(k);
}
}
// if an `id` has no ref in the `parents` array,
// take it as being the root node
if(impliedRoots.length === 1) {
k = impliedRoots[0];
cd.unshift({
hasImpliedRoot: true,
id: k,
pid: '',
label: k
});
} else {
return Lib.warn([
'Multiple implied roots, cannot build', trace.type, 'hierarchy of', trace.name + '.',
'These roots include:', impliedRoots.join(', ')
].join(' '));
}
} else if(parent2children[''].length > 1) {
var dummyId = Lib.randstr();
// if multiple rows linked to the root node,
// add dummy "root of roots" node to make d3 build the hierarchy successfully
for(var j = 0; j < cd.length; j++) {
if(cd[j].pid === '') {
cd[j].pid = dummyId;
}
}
cd.unshift({
hasMultipleRoots: true,
id: dummyId,
pid: '',
label: ''
});
}
// TODO might be better to replace stratify() with our own algorithm
var root;
try {
root = d3Hierarchy.stratify()
.id(function(d) { return d.id; })
.parentId(function(d) { return d.pid; })(cd);
} catch(e) {
return Lib.warn([
'Failed to build', trace.type, 'hierarchy of', trace.name + '.',
'Error:', e.message
].join(' '));
}
var hierarchy = d3Hierarchy.hierarchy(root);
var failed = false;
if(hasValues) {
switch(trace.branchvalues) {
case 'remainder':
hierarchy.sum(function(d) { return d.data.v; });
break;
case 'total':
hierarchy.each(function(d) {
var cdi = d.data.data;
var v = cdi.v;
if(d.children) {
var partialSum = d.children.reduce(function(a, c) {
return a + c.data.data.v;
}, 0);
// N.B. we must fill in `value` for generated sectors
// with the partialSum to compute the correct partition
if(cdi.hasImpliedRoot || cdi.hasMultipleRoots) {
v = partialSum;
}
if(v < partialSum * ALMOST_EQUAL) {
failed = true;
return Lib.warn([
'Total value for node', d.data.data.id, 'of', trace.name,
'is smaller than the sum of its children.',
'\nparent value =', v,
'\nchildren sum =', partialSum
].join(' '));
}
}
d.value = v;
});
break;
}
} else {
countDescendants(hierarchy, trace, {
branches: trace.count.indexOf('branches') !== -1,
leaves: trace.count.indexOf('leaves') !== -1
});
}
if(failed) return;
// TODO add way to sort by height also?
if(trace.sort) {
hierarchy.sort(function(a, b) { return b.value - a.value; });
}
var pullColor;
var scaleColor;
var colors = trace.marker.colors || [];
var hasColors = !!colors.length;
if(trace._hasColorscale) {
if(!hasColors) {
colors = hasValues ? trace.values : trace._values;
}
colorscaleCalc(gd, trace, {
vals: colors,
containerStr: 'marker',
cLetter: 'c'
});
scaleColor = makeColorScaleFn(trace.marker);
} else {
pullColor = makePullColorFn(fullLayout['_' + trace.type + 'colormap']);
}
// TODO keep track of 'root-children' (i.e. branch) for hover info etc.
hierarchy.each(function(d) {
var cdi = d.data.data;
// N.B. this mutates items in `cd`
cdi.color = trace._hasColorscale ?
scaleColor(colors[cdi.i]) :
pullColor(colors[cdi.i], cdi.id);
});
cd[0].hierarchy = hierarchy;
return cd;
};
/*
* `calc` filled in (and collated) explicit colors.
* Now we need to propagate these explicit colors to other traces,
* and fill in default colors.
* This is done after sorting, so we pick defaults
* in the order slices will be displayed
*/
exports._runCrossTraceCalc = function(desiredType, gd) {
var fullLayout = gd._fullLayout;
var calcdata = gd.calcdata;
var colorWay = fullLayout[desiredType + 'colorway'];
var colorMap = fullLayout['_' + desiredType + 'colormap'];
if(fullLayout['extend' + desiredType + 'colors']) {
colorWay = generateExtendedColors(colorWay,
desiredType === 'icicle' ? icicleExtendedColorWays :
desiredType === 'treemap' ? treemapExtendedColorWays :
sunburstExtendedColorWays
);
}
var dfltColorCount = 0;
var rootColor;
function pickColor(d) {
var cdi = d.data.data;
var id = cdi.id;
if(cdi.color === false) {
if(colorMap[id]) {
// have we seen this label and assigned a color to it in a previous trace?
cdi.color = colorMap[id];
} else if(d.parent) {
if(d.parent.parent) {
// from third-level on, inherit from parent
cdi.color = d.parent.data.data.color;
} else {
// pick new color for second level
colorMap[id] = cdi.color = colorWay[dfltColorCount % colorWay.length];
dfltColorCount++;
}
} else {
// set root color. no coloring by default.
cdi.color = rootColor;
}
}
}
for(var i = 0; i < calcdata.length; i++) {
var cd = calcdata[i];
var cd0 = cd[0];
if(cd0.trace.type === desiredType && cd0.hierarchy) {
rootColor = cd0.trace.root.color;
cd0.hierarchy.each(pickColor);
}
}
};
exports.crossTraceCalc = function(gd) {
return exports._runCrossTraceCalc('sunburst', gd);
};
function countDescendants(node, trace, opts) {
var nChild = 0;
var children = node.children;
if(children) {
var len = children.length;
for(var i = 0; i < len; i++) {
nChild += countDescendants(children[i], trace, opts);
}
if(opts.branches) nChild++; // count this branch
} else {
if(opts.leaves) nChild++; // count this leaf
}
// save to the node
node.value = node.data.data.value = nChild;
// save to the trace
if(!trace._values) trace._values = [];
trace._values[node.data.data.i] = nChild;
return nChild;
}
},{"../../components/colorscale":378,"../../constants/numerical":479,"../../lib":503,"../pie/calc":901,"d3-hierarchy":115,"fast-isnumeric":190}],1047:[function(_dereq_,module,exports){
'use strict';
module.exports = {
CLICK_TRANSITION_TIME: 750,
CLICK_TRANSITION_EASING: 'linear',
eventDataKeys: [
// string
'currentPath',
'root',
'entry',
// no need to add 'parent' here
// percentages i.e. ratios
'percentRoot',
'percentEntry',
'percentParent'
]
};
},{}],1048:[function(_dereq_,module,exports){
'use strict';
var Lib = _dereq_('../../lib');
var attributes = _dereq_('./attributes');
var handleDomainDefaults = _dereq_('../../plots/domain').defaults;
var handleText = _dereq_('../bar/defaults').handleText;
var Colorscale = _dereq_('../../components/colorscale');
var hasColorscale = Colorscale.hasColorscale;
var colorscaleDefaults = Colorscale.handleDefaults;
module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout) {
function coerce(attr, dflt) {
return Lib.coerce(traceIn, traceOut, attributes, attr, dflt);
}
var labels = coerce('labels');
var parents = coerce('parents');
if(!labels || !labels.length || !parents || !parents.length) {
traceOut.visible = false;
return;
}
var vals = coerce('values');
if(vals && vals.length) {
coerce('branchvalues');
} else {
coerce('count');
}
coerce('level');
coerce('maxdepth');
var lineWidth = coerce('marker.line.width');
if(lineWidth) coerce('marker.line.color', layout.paper_bgcolor);
coerce('marker.colors');
var withColorscale = traceOut._hasColorscale = (
hasColorscale(traceIn, 'marker', 'colors') ||
(traceIn.marker || {}).coloraxis // N.B. special logic to consider "values" colorscales
);
if(withColorscale) {
colorscaleDefaults(traceIn, traceOut, layout, coerce, {prefix: 'marker.', cLetter: 'c'});
}
coerce('leaf.opacity', withColorscale ? 1 : 0.7);
var text = coerce('text');
coerce('texttemplate');
if(!traceOut.texttemplate) coerce('textinfo', Array.isArray(text) ? 'text+label' : 'label');
coerce('hovertext');
coerce('hovertemplate');
var textposition = 'auto';
handleText(traceIn, traceOut, layout, coerce, textposition, {
moduleHasSelected: false,
moduleHasUnselected: false,
moduleHasConstrain: false,
moduleHasCliponaxis: false,
moduleHasTextangle: false,
moduleHasInsideanchor: false
});
coerce('insidetextorientation');
coerce('sort');
coerce('rotation');
coerce('root.color');
handleDomainDefaults(traceOut, layout, coerce);
// do not support transforms for now
traceOut._length = null;
};
},{"../../components/colorscale":378,"../../lib":503,"../../plots/domain":584,"../bar/defaults":652,"./attributes":1044}],1049:[function(_dereq_,module,exports){
'use strict';
var d3 = _dereq_('@plotly/d3');
var Registry = _dereq_('../../registry');
var appendArrayPointValue = _dereq_('../../components/fx/helpers').appendArrayPointValue;
var Fx = _dereq_('../../components/fx');
var Lib = _dereq_('../../lib');
var Events = _dereq_('../../lib/events');
var helpers = _dereq_('./helpers');
var pieHelpers = _dereq_('../pie/helpers');
var formatValue = pieHelpers.formatPieValue;
module.exports = function attachFxHandlers(sliceTop, entry, gd, cd, opts) {
var cd0 = cd[0];
var trace = cd0.trace;
var hierarchy = cd0.hierarchy;
var isSunburst = trace.type === 'sunburst';
var isTreemapOrIcicle =
trace.type === 'treemap' ||
trace.type === 'icicle';
// hover state vars
// have we drawn a hover label, so it should be cleared later
if(!('_hasHoverLabel' in trace)) trace._hasHoverLabel = false;
// have we emitted a hover event, so later an unhover event should be emitted
// note that click events do not depend on this - you can still get them
// with hovermode: false or if you were earlier dragging, then clicked
// in the same slice that you moused up in
if(!('_hasHoverEvent' in trace)) trace._hasHoverEvent = false;
var onMouseOver = function(pt) {
var fullLayoutNow = gd._fullLayout;
if(gd._dragging || fullLayoutNow.hovermode === false) return;
var traceNow = gd._fullData[trace.index];
var cdi = pt.data.data;
var ptNumber = cdi.i;
var isRoot = helpers.isHierarchyRoot(pt);
var parent = helpers.getParent(hierarchy, pt);
var val = helpers.getValue(pt);
var _cast = function(astr) {
return Lib.castOption(traceNow, ptNumber, astr);
};
var hovertemplate = _cast('hovertemplate');
var hoverinfo = Fx.castHoverinfo(traceNow, fullLayoutNow, ptNumber);
var separators = fullLayoutNow.separators;
var eventData;
if(hovertemplate || (hoverinfo && hoverinfo !== 'none' && hoverinfo !== 'skip')) {
var hoverCenterX;
var hoverCenterY;
if(isSunburst) {
hoverCenterX = cd0.cx + pt.pxmid[0] * (1 - pt.rInscribed);
hoverCenterY = cd0.cy + pt.pxmid[1] * (1 - pt.rInscribed);
}
if(isTreemapOrIcicle) {
hoverCenterX = pt._hoverX;
hoverCenterY = pt._hoverY;
}
var hoverPt = {};
var parts = [];
var thisText = [];
var hasFlag = function(flag) { return parts.indexOf(flag) !== -1; };
if(hoverinfo) {
parts = hoverinfo === 'all' ?
traceNow._module.attributes.hoverinfo.flags :
hoverinfo.split('+');
}
hoverPt.label = cdi.label;
if(hasFlag('label') && hoverPt.label) thisText.push(hoverPt.label);
if(cdi.hasOwnProperty('v')) {
hoverPt.value = cdi.v;
hoverPt.valueLabel = formatValue(hoverPt.value, separators);
if(hasFlag('value')) thisText.push(hoverPt.valueLabel);
}
hoverPt.currentPath = pt.currentPath = helpers.getPath(pt.data);
if(hasFlag('current path') && !isRoot) {
thisText.push(hoverPt.currentPath);
}
var tx;
var allPercents = [];
var insertPercent = function() {
if(allPercents.indexOf(tx) === -1) { // no need to add redundant info
thisText.push(tx);
allPercents.push(tx);
}
};
hoverPt.percentParent = pt.percentParent = val / helpers.getValue(parent);
hoverPt.parent = pt.parentString = helpers.getPtLabel(parent);
if(hasFlag('percent parent')) {
tx = helpers.formatPercent(hoverPt.percentParent, separators) + ' of ' + hoverPt.parent;
insertPercent();
}
hoverPt.percentEntry = pt.percentEntry = val / helpers.getValue(entry);
hoverPt.entry = pt.entry = helpers.getPtLabel(entry);
if(hasFlag('percent entry') && !isRoot && !pt.onPathbar) {
tx = helpers.formatPercent(hoverPt.percentEntry, separators) + ' of ' + hoverPt.entry;
insertPercent();
}
hoverPt.percentRoot = pt.percentRoot = val / helpers.getValue(hierarchy);
hoverPt.root = pt.root = helpers.getPtLabel(hierarchy);
if(hasFlag('percent root') && !isRoot) {
tx = helpers.formatPercent(hoverPt.percentRoot, separators) + ' of ' + hoverPt.root;
insertPercent();
}
hoverPt.text = _cast('hovertext') || _cast('text');
if(hasFlag('text')) {
tx = hoverPt.text;
if(Lib.isValidTextValue(tx)) thisText.push(tx);
}
eventData = [makeEventData(pt, traceNow, opts.eventDataKeys)];
var hoverItems = {
trace: traceNow,
y: hoverCenterY,
_x0: pt._x0,
_x1: pt._x1,
_y0: pt._y0,
_y1: pt._y1,
text: thisText.join('
'),
name: (hovertemplate || hasFlag('name')) ? traceNow.name : undefined,
color: _cast('hoverlabel.bgcolor') || cdi.color,
borderColor: _cast('hoverlabel.bordercolor'),
fontFamily: _cast('hoverlabel.font.family'),
fontSize: _cast('hoverlabel.font.size'),
fontColor: _cast('hoverlabel.font.color'),
nameLength: _cast('hoverlabel.namelength'),
textAlign: _cast('hoverlabel.align'),
hovertemplate: hovertemplate,
hovertemplateLabels: hoverPt,
eventData: eventData
};
if(isSunburst) {
hoverItems.x0 = hoverCenterX - pt.rInscribed * pt.rpx1;
hoverItems.x1 = hoverCenterX + pt.rInscribed * pt.rpx1;
hoverItems.idealAlign = pt.pxmid[0] < 0 ? 'left' : 'right';
}
if(isTreemapOrIcicle) {
hoverItems.x = hoverCenterX;
hoverItems.idealAlign = hoverCenterX < 0 ? 'left' : 'right';
}
var bbox = [];
Fx.loneHover(hoverItems, {
container: fullLayoutNow._hoverlayer.node(),
outerContainer: fullLayoutNow._paper.node(),
gd: gd,
inOut_bbox: bbox
});
eventData[0].bbox = bbox[0];
trace._hasHoverLabel = true;
}
if(isTreemapOrIcicle) {
var slice = sliceTop.select('path.surface');
opts.styleOne(slice, pt, traceNow, {
hovered: true
});
}
trace._hasHoverEvent = true;
gd.emit('plotly_hover', {
points: eventData || [makeEventData(pt, traceNow, opts.eventDataKeys)],
event: d3.event
});
};
var onMouseOut = function(evt) {
var fullLayoutNow = gd._fullLayout;
var traceNow = gd._fullData[trace.index];
var pt = d3.select(this).datum();
if(trace._hasHoverEvent) {
evt.originalEvent = d3.event;
gd.emit('plotly_unhover', {
points: [makeEventData(pt, traceNow, opts.eventDataKeys)],
event: d3.event
});
trace._hasHoverEvent = false;
}
if(trace._hasHoverLabel) {
Fx.loneUnhover(fullLayoutNow._hoverlayer.node());
trace._hasHoverLabel = false;
}
if(isTreemapOrIcicle) {
var slice = sliceTop.select('path.surface');
opts.styleOne(slice, pt, traceNow, {
hovered: false
});
}
};
var onClick = function(pt) {
// TODO: this does not support right-click. If we want to support it, we
// would likely need to change pie to use dragElement instead of straight
// mapbox event binding. Or perhaps better, make a simple wrapper with the
// right mousedown, mousemove, and mouseup handlers just for a left/right click
// mapbox would use this too.
var fullLayoutNow = gd._fullLayout;
var traceNow = gd._fullData[trace.index];
var noTransition = isSunburst && (helpers.isHierarchyRoot(pt) || helpers.isLeaf(pt));
var id = helpers.getPtId(pt);
var nextEntry = helpers.isEntry(pt) ?
helpers.findEntryWithChild(hierarchy, id) :
helpers.findEntryWithLevel(hierarchy, id);
var nextLevel = helpers.getPtId(nextEntry);
var typeClickEvtData = {
points: [makeEventData(pt, traceNow, opts.eventDataKeys)],
event: d3.event
};
if(!noTransition) typeClickEvtData.nextLevel = nextLevel;
var clickVal = Events.triggerHandler(gd, 'plotly_' + trace.type + 'click', typeClickEvtData);
if(clickVal !== false && fullLayoutNow.hovermode) {
gd._hoverdata = [makeEventData(pt, traceNow, opts.eventDataKeys)];
Fx.click(gd, d3.event);
}
// if click does not trigger a transition, we're done!
if(noTransition) return;
// if custom handler returns false, we're done!
if(clickVal === false) return;
// skip if triggered from dragging a nearby cartesian subplot
if(gd._dragging) return;
// skip during transitions, to avoid potential bugs
// we could remove this check later
if(gd._transitioning) return;
// store 'old' level in guiEdit stash, so that subsequent Plotly.react
// calls with the same uirevision can start from the same entry
Registry.call('_storeDirectGUIEdit', traceNow, fullLayoutNow._tracePreGUI[traceNow.uid], {
level: traceNow.level
});
var frame = {
data: [{level: nextLevel}],
traces: [trace.index]
};
var animOpts = {
frame: {
redraw: false,
duration: opts.transitionTime
},
transition: {
duration: opts.transitionTime,
easing: opts.transitionEasing
},
mode: 'immediate',
fromcurrent: true
};
Fx.loneUnhover(fullLayoutNow._hoverlayer.node());
Registry.call('animate', gd, frame, animOpts);
};
sliceTop.on('mouseover', onMouseOver);
sliceTop.on('mouseout', onMouseOut);
sliceTop.on('click', onClick);
};
function makeEventData(pt, trace, keys) {
var cdi = pt.data.data;
var out = {
curveNumber: trace.index,
pointNumber: cdi.i,
data: trace._input,
fullData: trace,
// TODO more things like 'children', 'siblings', 'hierarchy?
};
for(var i = 0; i < keys.length; i++) {
var key = keys[i];
if(key in pt) out[key] = pt[key];
}
// handle special case of parent
if('parentString' in pt && !helpers.isHierarchyRoot(pt)) out.parent = pt.parentString;
appendArrayPointValue(out, trace, cdi.i);
return out;
}
},{"../../components/fx":406,"../../components/fx/helpers":402,"../../lib":503,"../../lib/events":492,"../../registry":638,"../pie/helpers":904,"./helpers":1050,"@plotly/d3":58}],1050:[function(_dereq_,module,exports){
'use strict';
var Lib = _dereq_('../../lib');
var Color = _dereq_('../../components/color');
var setCursor = _dereq_('../../lib/setcursor');
var pieHelpers = _dereq_('../pie/helpers');
exports.findEntryWithLevel = function(hierarchy, level) {
var out;
if(level) {
hierarchy.eachAfter(function(pt) {
if(exports.getPtId(pt) === level) {
return out = pt.copy();
}
});
}
return out || hierarchy;
};
exports.findEntryWithChild = function(hierarchy, childId) {
var out;
hierarchy.eachAfter(function(pt) {
var children = pt.children || [];
for(var i = 0; i < children.length; i++) {
var child = children[i];
if(exports.getPtId(child) === childId) {
return out = pt.copy();
}
}
});
return out || hierarchy;
};
exports.isEntry = function(pt) {
return !pt.parent;
};
exports.isLeaf = function(pt) {
return !pt.children;
};
exports.getPtId = function(pt) {
return pt.data.data.id;
};
exports.getPtLabel = function(pt) {
return pt.data.data.label;
};
exports.getValue = function(d) {
return d.value;
};
exports.isHierarchyRoot = function(pt) {
return getParentId(pt) === '';
};
exports.setSliceCursor = function(sliceTop, gd, opts) {
var hide = opts.isTransitioning;
if(!hide) {
var pt = sliceTop.datum();
hide = (
(opts.hideOnRoot && exports.isHierarchyRoot(pt)) ||
(opts.hideOnLeaves && exports.isLeaf(pt))
);
}
setCursor(sliceTop, hide ? null : 'pointer');
};
function determineOutsideTextFont(trace, pt, layoutFont) {
return {
color: exports.getOutsideTextFontKey('color', trace, pt, layoutFont),
family: exports.getOutsideTextFontKey('family', trace, pt, layoutFont),
size: exports.getOutsideTextFontKey('size', trace, pt, layoutFont)
};
}
function determineInsideTextFont(trace, pt, layoutFont, opts) {
var onPathbar = (opts || {}).onPathbar;
var cdi = pt.data.data;
var ptNumber = cdi.i;
var customColor = Lib.castOption(trace, ptNumber,
(onPathbar ? 'pathbar.textfont' : 'insidetextfont') + '.color'
);
if(!customColor && trace._input.textfont) {
// Why not simply using trace.textfont? Because if not set, it
// defaults to layout.font which has a default color. But if
// textfont.color and insidetextfont.color don't supply a value,
// a contrasting color shall be used.
customColor = Lib.castOption(trace._input, ptNumber, 'textfont.color');
}
return {
color: customColor || Color.contrast(cdi.color),
family: exports.getInsideTextFontKey('family', trace, pt, layoutFont, opts),
size: exports.getInsideTextFontKey('size', trace, pt, layoutFont, opts)
};
}
exports.getInsideTextFontKey = function(keyStr, trace, pt, layoutFont, opts) {
var onPathbar = (opts || {}).onPathbar;
var cont = onPathbar ? 'pathbar.textfont' : 'insidetextfont';
var ptNumber = pt.data.data.i;
return (
Lib.castOption(trace, ptNumber, cont + '.' + keyStr) ||
Lib.castOption(trace, ptNumber, 'textfont.' + keyStr) ||
layoutFont.size
);
};
exports.getOutsideTextFontKey = function(keyStr, trace, pt, layoutFont) {
var ptNumber = pt.data.data.i;
return (
Lib.castOption(trace, ptNumber, 'outsidetextfont.' + keyStr) ||
Lib.castOption(trace, ptNumber, 'textfont.' + keyStr) ||
layoutFont.size
);
};
exports.isOutsideText = function(trace, pt) {
return !trace._hasColorscale && exports.isHierarchyRoot(pt);
};
exports.determineTextFont = function(trace, pt, layoutFont, opts) {
return exports.isOutsideText(trace, pt) ?
determineOutsideTextFont(trace, pt, layoutFont) :
determineInsideTextFont(trace, pt, layoutFont, opts);
};
exports.hasTransition = function(transitionOpts) {
// We could optimize hasTransition per trace,
// as sunburst, treemap & icicle have no cross-trace logic!
return !!(transitionOpts && transitionOpts.duration > 0);
};
exports.getMaxDepth = function(trace) {
return trace.maxdepth >= 0 ? trace.maxdepth : Infinity;
};
exports.isHeader = function(pt, trace) { // it is only used in treemap.
return !(exports.isLeaf(pt) || pt.depth === trace._maxDepth - 1);
};
function getParentId(pt) {
return pt.data.data.pid;
}
exports.getParent = function(hierarchy, pt) {
return exports.findEntryWithLevel(hierarchy, getParentId(pt));
};
exports.listPath = function(d, keyStr) {
var parent = d.parent;
if(!parent) return [];
var list = keyStr ? [parent.data[keyStr]] : [parent];
return exports.listPath(parent, keyStr).concat(list);
};
exports.getPath = function(d) {
return exports.listPath(d, 'label').join('/') + '/';
};
exports.formatValue = pieHelpers.formatPieValue;
// TODO: should combine the two in a separate PR - Also please note Lib.formatPercent should support separators.
exports.formatPercent = function(v, separators) {
var tx = Lib.formatPercent(v, 0); // use funnel(area) version
if(tx === '0%') tx = pieHelpers.formatPiePercent(v, separators); // use pie version
return tx;
};
},{"../../components/color":366,"../../lib":503,"../../lib/setcursor":524,"../pie/helpers":904}],1051:[function(_dereq_,module,exports){
'use strict';
module.exports = {
moduleType: 'trace',
name: 'sunburst',
basePlotModule: _dereq_('./base_plot'),
categories: [],
animatable: true,
attributes: _dereq_('./attributes'),
layoutAttributes: _dereq_('./layout_attributes'),
supplyDefaults: _dereq_('./defaults'),
supplyLayoutDefaults: _dereq_('./layout_defaults'),
calc: _dereq_('./calc').calc,
crossTraceCalc: _dereq_('./calc').crossTraceCalc,
plot: _dereq_('./plot').plot,
style: _dereq_('./style').style,
colorbar: _dereq_('../scatter/marker_colorbar'),
meta: {
}
};
},{"../scatter/marker_colorbar":943,"./attributes":1044,"./base_plot":1045,"./calc":1046,"./defaults":1048,"./layout_attributes":1052,"./layout_defaults":1053,"./plot":1054,"./style":1055}],1052:[function(_dereq_,module,exports){
'use strict';
module.exports = {
sunburstcolorway: {
valType: 'colorlist',
editType: 'calc',
},
extendsunburstcolors: {
valType: 'boolean',
dflt: true,
editType: 'calc',
}
};
},{}],1053:[function(_dereq_,module,exports){
'use strict';
var Lib = _dereq_('../../lib');
var layoutAttributes = _dereq_('./layout_attributes');
module.exports = function supplyLayoutDefaults(layoutIn, layoutOut) {
function coerce(attr, dflt) {
return Lib.coerce(layoutIn, layoutOut, layoutAttributes, attr, dflt);
}
coerce('sunburstcolorway', layoutOut.colorway);
coerce('extendsunburstcolors');
};
},{"../../lib":503,"./layout_attributes":1052}],1054:[function(_dereq_,module,exports){
'use strict';
var d3 = _dereq_('@plotly/d3');
var d3Hierarchy = _dereq_('d3-hierarchy');
var interpolate = _dereq_('d3-interpolate').interpolate;
var Drawing = _dereq_('../../components/drawing');
var Lib = _dereq_('../../lib');
var svgTextUtils = _dereq_('../../lib/svg_text_utils');
var uniformText = _dereq_('../bar/uniform_text');
var recordMinTextSize = uniformText.recordMinTextSize;
var clearMinTextSize = uniformText.clearMinTextSize;
var piePlot = _dereq_('../pie/plot');
var getRotationAngle = _dereq_('../pie/helpers').getRotationAngle;
var computeTransform = piePlot.computeTransform;
var transformInsideText = piePlot.transformInsideText;
var styleOne = _dereq_('./style').styleOne;
var resizeText = _dereq_('../bar/style').resizeText;
var attachFxHandlers = _dereq_('./fx');
var constants = _dereq_('./constants');
var helpers = _dereq_('./helpers');
exports.plot = function(gd, cdmodule, transitionOpts, makeOnCompleteCallback) {
var fullLayout = gd._fullLayout;
var layer = fullLayout._sunburstlayer;
var join, onComplete;
// If transition config is provided, then it is only a partial replot and traces not
// updated are removed.
var isFullReplot = !transitionOpts;
var hasTransition = !fullLayout.uniformtext.mode && helpers.hasTransition(transitionOpts);
clearMinTextSize('sunburst', fullLayout);
join = layer.selectAll('g.trace.sunburst')
.data(cdmodule, function(cd) { return cd[0].trace.uid; });
// using same 'stroke-linejoin' as pie traces
join.enter().append('g')
.classed('trace', true)
.classed('sunburst', true)
.attr('stroke-linejoin', 'round');
join.order();
if(hasTransition) {
if(makeOnCompleteCallback) {
// If it was passed a callback to register completion, make a callback. If
// this is created, then it must be executed on completion, otherwise the
// pos-transition redraw will not execute:
onComplete = makeOnCompleteCallback();
}
var transition = d3.transition()
.duration(transitionOpts.duration)
.ease(transitionOpts.easing)
.each('end', function() { onComplete && onComplete(); })
.each('interrupt', function() { onComplete && onComplete(); });
transition.each(function() {
// Must run the selection again since otherwise enters/updates get grouped together
// and these get executed out of order. Except we need them in order!
layer.selectAll('g.trace').each(function(cd) {
plotOne(gd, cd, this, transitionOpts);
});
});
} else {
join.each(function(cd) {
plotOne(gd, cd, this, transitionOpts);
});
if(fullLayout.uniformtext.mode) {
resizeText(gd, fullLayout._sunburstlayer.selectAll('.trace'), 'sunburst');
}
}
if(isFullReplot) {
join.exit().remove();
}
};
function plotOne(gd, cd, element, transitionOpts) {
var fullLayout = gd._fullLayout;
var hasTransition = !fullLayout.uniformtext.mode && helpers.hasTransition(transitionOpts);
var gTrace = d3.select(element);
var slices = gTrace.selectAll('g.slice');
var cd0 = cd[0];
var trace = cd0.trace;
var hierarchy = cd0.hierarchy;
var entry = helpers.findEntryWithLevel(hierarchy, trace.level);
var maxDepth = helpers.getMaxDepth(trace);
var gs = fullLayout._size;
var domain = trace.domain;
var vpw = gs.w * (domain.x[1] - domain.x[0]);
var vph = gs.h * (domain.y[1] - domain.y[0]);
var rMax = 0.5 * Math.min(vpw, vph);
var cx = cd0.cx = gs.l + gs.w * (domain.x[1] + domain.x[0]) / 2;
var cy = cd0.cy = gs.t + gs.h * (1 - domain.y[0]) - vph / 2;
if(!entry) {
return slices.remove();
}
// previous root 'pt' (can be empty)
var prevEntry = null;
// stash of 'previous' position data used by tweening functions
var prevLookup = {};
if(hasTransition) {
// Important: do this before binding new sliceData!
slices.each(function(pt) {
prevLookup[helpers.getPtId(pt)] = {
rpx0: pt.rpx0,
rpx1: pt.rpx1,
x0: pt.x0,
x1: pt.x1,
transform: pt.transform
};
if(!prevEntry && helpers.isEntry(pt)) {
prevEntry = pt;
}
});
}
// N.B. slice data isn't the calcdata,
// grab corresponding calcdata item in sliceData[i].data.data
var sliceData = partition(entry).descendants();
var maxHeight = entry.height + 1;
var yOffset = 0;
var cutoff = maxDepth;
// N.B. handle multiple-root special case
if(cd0.hasMultipleRoots && helpers.isHierarchyRoot(entry)) {
sliceData = sliceData.slice(1);
maxHeight -= 1;
yOffset = 1;
cutoff += 1;
}
// filter out slices that won't show up on graph
sliceData = sliceData.filter(function(pt) { return pt.y1 <= cutoff; });
var baseX = getRotationAngle(trace.rotation);
if(baseX) {
sliceData.forEach(function(pt) {
pt.x0 += baseX;
pt.x1 += baseX;
});
}
// partition span ('y') to sector radial px value
var maxY = Math.min(maxHeight, maxDepth);
var y2rpx = function(y) { return (y - yOffset) / maxY * rMax; };
// (radial px value, partition angle ('x')) to px [x,y]
var rx2px = function(r, x) { return [r * Math.cos(x), -r * Math.sin(x)]; };
// slice path generation fn
var pathSlice = function(d) { return Lib.pathAnnulus(d.rpx0, d.rpx1, d.x0, d.x1, cx, cy); };
// slice text translate x/y
var getTargetX = function(d) { return cx + getTextXY(d)[0] * (d.transform.rCenter || 0) + (d.transform.x || 0); };
var getTargetY = function(d) { return cy + getTextXY(d)[1] * (d.transform.rCenter || 0) + (d.transform.y || 0); };
slices = slices.data(sliceData, helpers.getPtId);
slices.enter().append('g')
.classed('slice', true);
if(hasTransition) {
slices.exit().transition()
.each(function() {
var sliceTop = d3.select(this);
var slicePath = sliceTop.select('path.surface');
slicePath.transition().attrTween('d', function(pt2) {
var interp = makeExitSliceInterpolator(pt2);
return function(t) { return pathSlice(interp(t)); };
});
var sliceTextGroup = sliceTop.select('g.slicetext');
sliceTextGroup.attr('opacity', 0);
})
.remove();
} else {
slices.exit().remove();
}
slices.order();
// next x1 (i.e. sector end angle) of previous entry
var nextX1ofPrevEntry = null;
if(hasTransition && prevEntry) {
var prevEntryId = helpers.getPtId(prevEntry);
slices.each(function(pt) {
if(nextX1ofPrevEntry === null && (helpers.getPtId(pt) === prevEntryId)) {
nextX1ofPrevEntry = pt.x1;
}
});
}
var updateSlices = slices;
if(hasTransition) {
updateSlices = updateSlices.transition().each('end', function() {
// N.B. gd._transitioning is (still) *true* by the time
// transition updates get here
var sliceTop = d3.select(this);
helpers.setSliceCursor(sliceTop, gd, {
hideOnRoot: true,
hideOnLeaves: true,
isTransitioning: false
});
});
}
updateSlices.each(function(pt) {
var sliceTop = d3.select(this);
var slicePath = Lib.ensureSingle(sliceTop, 'path', 'surface', function(s) {
s.style('pointer-events', 'all');
});
pt.rpx0 = y2rpx(pt.y0);
pt.rpx1 = y2rpx(pt.y1);
pt.xmid = (pt.x0 + pt.x1) / 2;
pt.pxmid = rx2px(pt.rpx1, pt.xmid);
pt.midangle = -(pt.xmid - Math.PI / 2);
pt.startangle = -(pt.x0 - Math.PI / 2);
pt.stopangle = -(pt.x1 - Math.PI / 2);
pt.halfangle = 0.5 * Math.min(Lib.angleDelta(pt.x0, pt.x1) || Math.PI, Math.PI);
pt.ring = 1 - (pt.rpx0 / pt.rpx1);
pt.rInscribed = getInscribedRadiusFraction(pt, trace);
if(hasTransition) {
slicePath.transition().attrTween('d', function(pt2) {
var interp = makeUpdateSliceInterpolator(pt2);
return function(t) { return pathSlice(interp(t)); };
});
} else {
slicePath.attr('d', pathSlice);
}
sliceTop
.call(attachFxHandlers, entry, gd, cd, {
eventDataKeys: constants.eventDataKeys,
transitionTime: constants.CLICK_TRANSITION_TIME,
transitionEasing: constants.CLICK_TRANSITION_EASING
})
.call(helpers.setSliceCursor, gd, {
hideOnRoot: true,
hideOnLeaves: true,
isTransitioning: gd._transitioning
});
slicePath.call(styleOne, pt, trace);
var sliceTextGroup = Lib.ensureSingle(sliceTop, 'g', 'slicetext');
var sliceText = Lib.ensureSingle(sliceTextGroup, 'text', '', function(s) {
// prohibit tex interpretation until we can handle
// tex and regular text together
s.attr('data-notex', 1);
});
var font = Lib.ensureUniformFontSize(gd, helpers.determineTextFont(trace, pt, fullLayout.font));
sliceText.text(exports.formatSliceLabel(pt, entry, trace, cd, fullLayout))
.classed('slicetext', true)
.attr('text-anchor', 'middle')
.call(Drawing.font, font)
.call(svgTextUtils.convertToTspans, gd);
// position the text relative to the slice
var textBB = Drawing.bBox(sliceText.node());
pt.transform = transformInsideText(textBB, pt, cd0);
pt.transform.targetX = getTargetX(pt);
pt.transform.targetY = getTargetY(pt);
var strTransform = function(d, textBB) {
var transform = d.transform;
computeTransform(transform, textBB);
transform.fontSize = font.size;
recordMinTextSize(trace.type, transform, fullLayout);
return Lib.getTextTransform(transform);
};
if(hasTransition) {
sliceText.transition().attrTween('transform', function(pt2) {
var interp = makeUpdateTextInterpolator(pt2);
return function(t) { return strTransform(interp(t), textBB); };
});
} else {
sliceText.attr('transform', strTransform(pt, textBB));
}
});
function makeExitSliceInterpolator(pt) {
var id = helpers.getPtId(pt);
var prev = prevLookup[id];
var entryPrev = prevLookup[helpers.getPtId(entry)];
var next;
if(entryPrev) {
var a = (pt.x1 > entryPrev.x1 ? 2 * Math.PI : 0) + baseX;
// if pt to remove:
// - if 'below' where the root-node used to be: shrink it radially inward
// - otherwise, collapse it clockwise or counterclockwise which ever is shortest to theta=0
next = pt.rpx1 < entryPrev.rpx1 ?
{x0: pt.x0, x1: pt.x1, rpx0: 0, rpx1: 0} :
{x0: a, x1: a, rpx0: pt.rpx0, rpx1: pt.rpx1};
} else {
// this happens when maxdepth is set, when leaves must
// be removed and the rootPt is new (i.e. does not have a 'prev' object)
var parent;
var parentId = helpers.getPtId(pt.parent);
slices.each(function(pt2) {
if(helpers.getPtId(pt2) === parentId) {
return parent = pt2;
}
});
var parentChildren = parent.children;
var ci;
parentChildren.forEach(function(pt2, i) {
if(helpers.getPtId(pt2) === id) {
return ci = i;
}
});
var n = parentChildren.length;
var interp = interpolate(parent.x0, parent.x1);
next = {
rpx0: rMax, rpx1: rMax,
x0: interp(ci / n), x1: interp((ci + 1) / n)
};
}
return interpolate(prev, next);
}
function makeUpdateSliceInterpolator(pt) {
var prev0 = prevLookup[helpers.getPtId(pt)];
var prev;
var next = {x0: pt.x0, x1: pt.x1, rpx0: pt.rpx0, rpx1: pt.rpx1};
if(prev0) {
// if pt already on graph, this is easy
prev = prev0;
} else {
// for new pts:
if(prevEntry) {
// if trace was visible before
if(pt.parent) {
if(nextX1ofPrevEntry) {
// if new branch, twist it in clockwise or
// counterclockwise which ever is shorter to
// its final angle
var a = (pt.x1 > nextX1ofPrevEntry ? 2 * Math.PI : 0) + baseX;
prev = {x0: a, x1: a};
} else {
// if new leaf (when maxdepth is set),
// grow it radially and angularly from
// its parent node
prev = {rpx0: rMax, rpx1: rMax};
Lib.extendFlat(prev, interpX0X1FromParent(pt));
}
} else {
// if new root-node, grow it radially
prev = {rpx0: 0, rpx1: 0};
}
} else {
// start sector of new traces from theta=0
prev = {x0: baseX, x1: baseX};
}
}
return interpolate(prev, next);
}
function makeUpdateTextInterpolator(pt) {
var prev0 = prevLookup[helpers.getPtId(pt)];
var prev;
var transform = pt.transform;
if(prev0) {
prev = prev0;
} else {
prev = {
rpx1: pt.rpx1,
transform: {
textPosAngle: transform.textPosAngle,
scale: 0,
rotate: transform.rotate,
rCenter: transform.rCenter,
x: transform.x,
y: transform.y
}
};
// for new pts:
if(prevEntry) {
// if trace was visible before
if(pt.parent) {
if(nextX1ofPrevEntry) {
// if new branch, twist it in clockwise or
// counterclockwise which ever is shorter to
// its final angle
var a = pt.x1 > nextX1ofPrevEntry ? 2 * Math.PI : 0;
prev.x0 = prev.x1 = a;
} else {
// if leaf
Lib.extendFlat(prev, interpX0X1FromParent(pt));
}
} else {
// if new root-node
prev.x0 = prev.x1 = baseX;
}
} else {
// on new traces
prev.x0 = prev.x1 = baseX;
}
}
var textPosAngleFn = interpolate(prev.transform.textPosAngle, pt.transform.textPosAngle);
var rpx1Fn = interpolate(prev.rpx1, pt.rpx1);
var x0Fn = interpolate(prev.x0, pt.x0);
var x1Fn = interpolate(prev.x1, pt.x1);
var scaleFn = interpolate(prev.transform.scale, transform.scale);
var rotateFn = interpolate(prev.transform.rotate, transform.rotate);
// smooth out start/end from entry, to try to keep text inside sector
// while keeping transition smooth
var pow = transform.rCenter === 0 ? 3 :
prev.transform.rCenter === 0 ? 1 / 3 :
1;
var _rCenterFn = interpolate(prev.transform.rCenter, transform.rCenter);
var rCenterFn = function(t) { return _rCenterFn(Math.pow(t, pow)); };
return function(t) {
var rpx1 = rpx1Fn(t);
var x0 = x0Fn(t);
var x1 = x1Fn(t);
var rCenter = rCenterFn(t);
var pxmid = rx2px(rpx1, (x0 + x1) / 2);
var textPosAngle = textPosAngleFn(t);
var d = {
pxmid: pxmid,
rpx1: rpx1,
transform: {
textPosAngle: textPosAngle,
rCenter: rCenter,
x: transform.x,
y: transform.y
}
};
recordMinTextSize(trace.type, transform, fullLayout);
return {
transform: {
targetX: getTargetX(d),
targetY: getTargetY(d),
scale: scaleFn(t),
rotate: rotateFn(t),
rCenter: rCenter
}
};
};
}
function interpX0X1FromParent(pt) {
var parent = pt.parent;
var parentPrev = prevLookup[helpers.getPtId(parent)];
var out = {};
if(parentPrev) {
// if parent is visible
var parentChildren = parent.children;
var ci = parentChildren.indexOf(pt);
var n = parentChildren.length;
var interp = interpolate(parentPrev.x0, parentPrev.x1);
out.x0 = interp(ci / n);
out.x1 = interp(ci / n);
} else {
// w/o visible parent
// TODO !!! HOW ???
out.x0 = out.x1 = 0;
}
return out;
}
}
// x[0-1] keys are angles [radians]
// y[0-1] keys are hierarchy heights [integers]
function partition(entry) {
return d3Hierarchy.partition()
.size([2 * Math.PI, entry.height + 1])(entry);
}
exports.formatSliceLabel = function(pt, entry, trace, cd, fullLayout) {
var texttemplate = trace.texttemplate;
var textinfo = trace.textinfo;
if(!texttemplate && (!textinfo || textinfo === 'none')) {
return '';
}
var separators = fullLayout.separators;
var cd0 = cd[0];
var cdi = pt.data.data;
var hierarchy = cd0.hierarchy;
var isRoot = helpers.isHierarchyRoot(pt);
var parent = helpers.getParent(hierarchy, pt);
var val = helpers.getValue(pt);
if(!texttemplate) {
var parts = textinfo.split('+');
var hasFlag = function(flag) { return parts.indexOf(flag) !== -1; };
var thisText = [];
var tx;
if(hasFlag('label') && cdi.label) {
thisText.push(cdi.label);
}
if(cdi.hasOwnProperty('v') && hasFlag('value')) {
thisText.push(helpers.formatValue(cdi.v, separators));
}
if(!isRoot) {
if(hasFlag('current path')) {
thisText.push(helpers.getPath(pt.data));
}
var nPercent = 0;
if(hasFlag('percent parent')) nPercent++;
if(hasFlag('percent entry')) nPercent++;
if(hasFlag('percent root')) nPercent++;
var hasMultiplePercents = nPercent > 1;
if(nPercent) {
var percent;
var addPercent = function(key) {
tx = helpers.formatPercent(percent, separators);
if(hasMultiplePercents) tx += ' of ' + key;
thisText.push(tx);
};
if(hasFlag('percent parent') && !isRoot) {
percent = val / helpers.getValue(parent);
addPercent('parent');
}
if(hasFlag('percent entry')) {
percent = val / helpers.getValue(entry);
addPercent('entry');
}
if(hasFlag('percent root')) {
percent = val / helpers.getValue(hierarchy);
addPercent('root');
}
}
}
if(hasFlag('text')) {
tx = Lib.castOption(trace, cdi.i, 'text');
if(Lib.isValidTextValue(tx)) thisText.push(tx);
}
return thisText.join('
');
}
var txt = Lib.castOption(trace, cdi.i, 'texttemplate');
if(!txt) return '';
var obj = {};
if(cdi.label) obj.label = cdi.label;
if(cdi.hasOwnProperty('v')) {
obj.value = cdi.v;
obj.valueLabel = helpers.formatValue(cdi.v, separators);
}
obj.currentPath = helpers.getPath(pt.data);
if(!isRoot) {
obj.percentParent = val / helpers.getValue(parent);
obj.percentParentLabel = helpers.formatPercent(
obj.percentParent, separators
);
obj.parent = helpers.getPtLabel(parent);
}
obj.percentEntry = val / helpers.getValue(entry);
obj.percentEntryLabel = helpers.formatPercent(
obj.percentEntry, separators
);
obj.entry = helpers.getPtLabel(entry);
obj.percentRoot = val / helpers.getValue(hierarchy);
obj.percentRootLabel = helpers.formatPercent(
obj.percentRoot, separators
);
obj.root = helpers.getPtLabel(hierarchy);
if(cdi.hasOwnProperty('color')) {
obj.color = cdi.color;
}
var ptTx = Lib.castOption(trace, cdi.i, 'text');
if(Lib.isValidTextValue(ptTx) || ptTx === '') obj.text = ptTx;
obj.customdata = Lib.castOption(trace, cdi.i, 'customdata');
return Lib.texttemplateString(txt, obj, fullLayout._d3locale, obj, trace._meta || {});
};
function getInscribedRadiusFraction(pt) {
if(pt.rpx0 === 0 && Lib.isFullCircle([pt.x0, pt.x1])) {
// special case of 100% with no hole
return 1;
} else {
return Math.max(0, Math.min(
1 / (1 + 1 / Math.sin(pt.halfangle)),
pt.ring / 2
));
}
}
function getTextXY(d) {
return getCoords(d.rpx1, d.transform.textPosAngle);
}
function getCoords(r, angle) {
return [r * Math.sin(angle), -r * Math.cos(angle)];
}
},{"../../components/drawing":388,"../../lib":503,"../../lib/svg_text_utils":529,"../bar/style":662,"../bar/uniform_text":664,"../pie/helpers":904,"../pie/plot":908,"./constants":1047,"./fx":1049,"./helpers":1050,"./style":1055,"@plotly/d3":58,"d3-hierarchy":115,"d3-interpolate":116}],1055:[function(_dereq_,module,exports){
'use strict';
var d3 = _dereq_('@plotly/d3');
var Color = _dereq_('../../components/color');
var Lib = _dereq_('../../lib');
var resizeText = _dereq_('../bar/uniform_text').resizeText;
function style(gd) {
var s = gd._fullLayout._sunburstlayer.selectAll('.trace');
resizeText(gd, s, 'sunburst');
s.each(function(cd) {
var gTrace = d3.select(this);
var cd0 = cd[0];
var trace = cd0.trace;
gTrace.style('opacity', trace.opacity);
gTrace.selectAll('path.surface').each(function(pt) {
d3.select(this).call(styleOne, pt, trace);
});
});
}
function styleOne(s, pt, trace) {
var cdi = pt.data.data;
var isLeaf = !pt.children;
var ptNumber = cdi.i;
var lineColor = Lib.castOption(trace, ptNumber, 'marker.line.color') || Color.defaultLine;
var lineWidth = Lib.castOption(trace, ptNumber, 'marker.line.width') || 0;
s.style('stroke-width', lineWidth)
.call(Color.fill, cdi.color)
.call(Color.stroke, lineColor)
.style('opacity', isLeaf ? trace.leaf.opacity : null);
}
module.exports = {
style: style,
styleOne: styleOne
};
},{"../../components/color":366,"../../lib":503,"../bar/uniform_text":664,"@plotly/d3":58}],1056:[function(_dereq_,module,exports){
'use strict';
var Color = _dereq_('../../components/color');
var colorScaleAttrs = _dereq_('../../components/colorscale/attributes');
var axisHoverFormat = _dereq_('../../plots/cartesian/axis_format_attributes').axisHoverFormat;
var hovertemplateAttrs = _dereq_('../../plots/template_attributes').hovertemplateAttrs;
var baseAttrs = _dereq_('../../plots/attributes');
var extendFlat = _dereq_('../../lib/extend').extendFlat;
var overrideAll = _dereq_('../../plot_api/edit_types').overrideAll;
function makeContourProjAttr(axLetter) {
return {
valType: 'boolean',
dflt: false,
};
}
function makeContourAttr(axLetter) {
return {
show: {
valType: 'boolean',
dflt: false,
},
start: {
valType: 'number',
dflt: null,
editType: 'plot',
// impliedEdits: {'^autocontour': false},
},
end: {
valType: 'number',
dflt: null,
editType: 'plot',
// impliedEdits: {'^autocontour': false},
},
size: {
valType: 'number',
dflt: null,
min: 0,
editType: 'plot',
// impliedEdits: {'^autocontour': false},
},
project: {
x: makeContourProjAttr('x'),
y: makeContourProjAttr('y'),
z: makeContourProjAttr('z')
},
color: {
valType: 'color',
dflt: Color.defaultLine,
},
usecolormap: {
valType: 'boolean',
dflt: false,
},
width: {
valType: 'number',
min: 1,
max: 16,
dflt: 2,
},
highlight: {
valType: 'boolean',
dflt: true,
},
highlightcolor: {
valType: 'color',
dflt: Color.defaultLine,
},
highlightwidth: {
valType: 'number',
min: 1,
max: 16,
dflt: 2,
}
};
}
var attrs = module.exports = overrideAll(extendFlat({
z: {
valType: 'data_array',
},
x: {
valType: 'data_array',
},
y: {
valType: 'data_array',
},
text: {
valType: 'string',
dflt: '',
arrayOk: true,
},
hovertext: {
valType: 'string',
dflt: '',
arrayOk: true,
},
hovertemplate: hovertemplateAttrs(),
xhoverformat: axisHoverFormat('x'),
yhoverformat: axisHoverFormat('y'),
zhoverformat: axisHoverFormat('z'),
connectgaps: {
valType: 'boolean',
dflt: false,
editType: 'calc',
},
surfacecolor: {
valType: 'data_array',
},
},
colorScaleAttrs('', {
colorAttr: 'z or surfacecolor',
showScaleDflt: true,
autoColorDflt: false,
editTypeOverride: 'calc'
}), {
contours: {
x: makeContourAttr('x'),
y: makeContourAttr('y'),
z: makeContourAttr('z')
},
hidesurface: {
valType: 'boolean',
dflt: false,
},
lightposition: {
x: {
valType: 'number',
min: -1e5,
max: 1e5,
dflt: 10,
},
y: {
valType: 'number',
min: -1e5,
max: 1e5,
dflt: 1e4,
},
z: {
valType: 'number',
min: -1e5,
max: 1e5,
dflt: 0,
}
},
lighting: {
ambient: {
valType: 'number',
min: 0.00,
max: 1.0,
dflt: 0.8,
},
diffuse: {
valType: 'number',
min: 0.00,
max: 1.00,
dflt: 0.8,
},
specular: {
valType: 'number',
min: 0.00,
max: 2.00,
dflt: 0.05,
},
roughness: {
valType: 'number',
min: 0.00,
max: 1.00,
dflt: 0.5,
},
fresnel: {
valType: 'number',
min: 0.00,
max: 5.00,
dflt: 0.2,
}
},
opacity: {
valType: 'number',
min: 0,
max: 1,
dflt: 1,
},
opacityscale: {
valType: 'any',
editType: 'calc',
},
_deprecated: {
zauto: extendFlat({}, colorScaleAttrs.zauto, {
}),
zmin: extendFlat({}, colorScaleAttrs.zmin, {
}),
zmax: extendFlat({}, colorScaleAttrs.zmax, {
})
},
hoverinfo: extendFlat({}, baseAttrs.hoverinfo),
showlegend: extendFlat({}, baseAttrs.showlegend, {dflt: false}),
}), 'calc', 'nested');
attrs.x.editType = attrs.y.editType = attrs.z.editType = 'calc+clearAxisTypes';
attrs.transforms = undefined;
},{"../../components/color":366,"../../components/colorscale/attributes":373,"../../lib/extend":493,"../../plot_api/edit_types":536,"../../plots/attributes":550,"../../plots/cartesian/axis_format_attributes":557,"../../plots/template_attributes":633}],1057:[function(_dereq_,module,exports){
'use strict';
var colorscaleCalc = _dereq_('../../components/colorscale/calc');
// Compute auto-z and autocolorscale if applicable
module.exports = function calc(gd, trace) {
if(trace.surfacecolor) {
colorscaleCalc(gd, trace, {
vals: trace.surfacecolor,
containerStr: '',
cLetter: 'c'
});
} else {
colorscaleCalc(gd, trace, {
vals: trace.z,
containerStr: '',
cLetter: 'c'
});
}
};
},{"../../components/colorscale/calc":374}],1058:[function(_dereq_,module,exports){
'use strict';
var createSurface = _dereq_('../../../stackgl_modules').gl_surface3d;
var ndarray = _dereq_('../../../stackgl_modules').ndarray;
var ndarrayInterp2d = _dereq_('../../../stackgl_modules').ndarray_linear_interpolate.d2;
var interp2d = _dereq_('../heatmap/interp2d');
var findEmpties = _dereq_('../heatmap/find_empties');
var isArrayOrTypedArray = _dereq_('../../lib').isArrayOrTypedArray;
var parseColorScale = _dereq_('../../lib/gl_format_color').parseColorScale;
var str2RgbaArray = _dereq_('../../lib/str2rgbarray');
var extractOpts = _dereq_('../../components/colorscale').extractOpts;
function SurfaceTrace(scene, surface, uid) {
this.scene = scene;
this.uid = uid;
this.surface = surface;
this.data = null;
this.showContour = [false, false, false];
this.contourStart = [null, null, null];
this.contourEnd = [null, null, null];
this.contourSize = [0, 0, 0];
this.minValues = [Infinity, Infinity, Infinity];
this.maxValues = [-Infinity, -Infinity, -Infinity];
this.dataScaleX = 1.0;
this.dataScaleY = 1.0;
this.refineData = true;
this.objectOffset = [0, 0, 0];
}
var proto = SurfaceTrace.prototype;
proto.getXat = function(a, b, calendar, axis) {
var v = (
(!isArrayOrTypedArray(this.data.x)) ?
a :
(isArrayOrTypedArray(this.data.x[0])) ?
this.data.x[b][a] :
this.data.x[a]
);
return (calendar === undefined) ? v : axis.d2l(v, 0, calendar);
};
proto.getYat = function(a, b, calendar, axis) {
var v = (
(!isArrayOrTypedArray(this.data.y)) ?
b :
(isArrayOrTypedArray(this.data.y[0])) ?
this.data.y[b][a] :
this.data.y[b]
);
return (calendar === undefined) ? v : axis.d2l(v, 0, calendar);
};
proto.getZat = function(a, b, calendar, axis) {
var v = this.data.z[b][a];
if(v === null && this.data.connectgaps && this.data._interpolatedZ) {
v = this.data._interpolatedZ[b][a];
}
return (calendar === undefined) ? v : axis.d2l(v, 0, calendar);
};
proto.handlePick = function(selection) {
if(selection.object === this.surface) {
var xRatio = (selection.data.index[0] - 1) / this.dataScaleX - 1;
var yRatio = (selection.data.index[1] - 1) / this.dataScaleY - 1;
var j = Math.max(Math.min(Math.round(xRatio), this.data.z[0].length - 1), 0);
var k = Math.max(Math.min(Math.round(yRatio), this.data._ylength - 1), 0);
selection.index = [j, k];
selection.traceCoordinate = [
this.getXat(j, k),
this.getYat(j, k),
this.getZat(j, k)
];
selection.dataCoordinate = [
this.getXat(j, k, this.data.xcalendar, this.scene.fullSceneLayout.xaxis),
this.getYat(j, k, this.data.ycalendar, this.scene.fullSceneLayout.yaxis),
this.getZat(j, k, this.data.zcalendar, this.scene.fullSceneLayout.zaxis)
];
for(var i = 0; i < 3; i++) {
var v = selection.dataCoordinate[i];
if(v !== null && v !== undefined) {
selection.dataCoordinate[i] *= this.scene.dataScale[i];
}
}
var text = this.data.hovertext || this.data.text;
if(Array.isArray(text) && text[k] && text[k][j] !== undefined) {
selection.textLabel = text[k][j];
} else if(text) {
selection.textLabel = text;
} else {
selection.textLabel = '';
}
selection.data.dataCoordinate = selection.dataCoordinate.slice();
this.surface.highlight(selection.data);
// Snap spikes to data coordinate
this.scene.glplot.spikes.position = selection.dataCoordinate;
return true;
}
};
function isColormapCircular(colormap) {
var first = colormap[0].rgb;
var last = colormap[colormap.length - 1].rgb;
return (
first[0] === last[0] &&
first[1] === last[1] &&
first[2] === last[2] &&
first[3] === last[3]
);
}
var shortPrimes = [
2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97,
101, 103, 107, 109, 113, 127, 131, 137, 139, 149, 151, 157, 163, 167, 173, 179, 181, 191, 193, 197, 199,
211, 223, 227, 229, 233, 239, 241, 251, 257, 263, 269, 271, 277, 281, 283, 293,
307, 311, 313, 317, 331, 337, 347, 349, 353, 359, 367, 373, 379, 383, 389, 397,
401, 409, 419, 421, 431, 433, 439, 443, 449, 457, 461, 463, 467, 479, 487, 491, 499,
503, 509, 521, 523, 541, 547, 557, 563, 569, 571, 577, 587, 593, 599,
601, 607, 613, 617, 619, 631, 641, 643, 647, 653, 659, 661, 673, 677, 683, 691,
701, 709, 719, 727, 733, 739, 743, 751, 757, 761, 769, 773, 787, 797,
809, 811, 821, 823, 827, 829, 839, 853, 857, 859, 863, 877, 881, 883, 887,
907, 911, 919, 929, 937, 941, 947, 953, 967, 971, 977, 983, 991, 997,
1009, 1013, 1019, 1021, 1031, 1033, 1039, 1049, 1051, 1061, 1063, 1069, 1087, 1091, 1093, 1097,
1103, 1109, 1117, 1123, 1129, 1151, 1153, 1163, 1171, 1181, 1187, 1193,
1201, 1213, 1217, 1223, 1229, 1231, 1237, 1249, 1259, 1277, 1279, 1283, 1289, 1291, 1297,
1301, 1303, 1307, 1319, 1321, 1327, 1361, 1367, 1373, 1381, 1399,
1409, 1423, 1427, 1429, 1433, 1439, 1447, 1451, 1453, 1459, 1471, 1481, 1483, 1487, 1489, 1493, 1499,
1511, 1523, 1531, 1543, 1549, 1553, 1559, 1567, 1571, 1579, 1583, 1597,
1601, 1607, 1609, 1613, 1619, 1621, 1627, 1637, 1657, 1663, 1667, 1669, 1693, 1697, 1699,
1709, 1721, 1723, 1733, 1741, 1747, 1753, 1759, 1777, 1783, 1787, 1789,
1801, 1811, 1823, 1831, 1847, 1861, 1867, 1871, 1873, 1877, 1879, 1889,
1901, 1907, 1913, 1931, 1933, 1949, 1951, 1973, 1979, 1987, 1993, 1997, 1999,
2003, 2011, 2017, 2027, 2029, 2039, 2053, 2063, 2069, 2081, 2083, 2087, 2089, 2099,
2111, 2113, 2129, 2131, 2137, 2141, 2143, 2153, 2161, 2179,
2203, 2207, 2213, 2221, 2237, 2239, 2243, 2251, 2267, 2269, 2273, 2281, 2287, 2293, 2297,
2309, 2311, 2333, 2339, 2341, 2347, 2351, 2357, 2371, 2377, 2381, 2383, 2389, 2393, 2399,
2411, 2417, 2423, 2437, 2441, 2447, 2459, 2467, 2473, 2477,
2503, 2521, 2531, 2539, 2543, 2549, 2551, 2557, 2579, 2591, 2593,
2609, 2617, 2621, 2633, 2647, 2657, 2659, 2663, 2671, 2677, 2683, 2687, 2689, 2693, 2699,
2707, 2711, 2713, 2719, 2729, 2731, 2741, 2749, 2753, 2767, 2777, 2789, 2791, 2797,
2801, 2803, 2819, 2833, 2837, 2843, 2851, 2857, 2861, 2879, 2887, 2897,
2903, 2909, 2917, 2927, 2939, 2953, 2957, 2963, 2969, 2971, 2999
];
function getPow(a, b) {
if(a < b) return 0;
var n = 0;
while(Math.floor(a % b) === 0) {
a /= b;
n++;
}
return n;
}
function getFactors(a) {
var powers = [];
for(var i = 0; i < shortPrimes.length; i++) {
var b = shortPrimes[i];
powers.push(
getPow(a, b)
);
}
return powers;
}
function smallestDivisor(a) {
var A = getFactors(a);
var result = a;
for(var i = 0; i < shortPrimes.length; i++) {
if(A[i] > 0) {
result = shortPrimes[i];
break;
}
}
return result;
}
function leastCommonMultiple(a, b) {
if(a < 1 || b < 1) return undefined;
var A = getFactors(a);
var B = getFactors(b);
var n = 1;
for(var i = 0; i < shortPrimes.length; i++) {
n *= Math.pow(
shortPrimes[i], Math.max(A[i], B[i])
);
}
return n;
}
function arrayLCM(A) {
if(A.length === 0) return undefined;
var n = 1;
for(var i = 0; i < A.length; i++) {
n = leastCommonMultiple(n, A[i]);
}
return n;
}
proto.calcXnums = function(xlen) {
var i;
var nums = [];
for(i = 1; i < xlen; i++) {
var a = this.getXat(i - 1, 0);
var b = this.getXat(i, 0);
if(b !== a &&
a !== undefined && a !== null &&
b !== undefined && b !== null) {
nums[i - 1] = Math.abs(b - a);
} else {
nums[i - 1] = 0;
}
}
var totalDist = 0;
for(i = 1; i < xlen; i++) {
totalDist += nums[i - 1];
}
for(i = 1; i < xlen; i++) {
if(nums[i - 1] === 0) {
nums[i - 1] = 1;
} else {
nums[i - 1] = Math.round(totalDist / nums[i - 1]);
}
}
return nums;
};
proto.calcYnums = function(ylen) {
var i;
var nums = [];
for(i = 1; i < ylen; i++) {
var a = this.getYat(0, i - 1);
var b = this.getYat(0, i);
if(b !== a &&
a !== undefined && a !== null &&
b !== undefined && b !== null) {
nums[i - 1] = Math.abs(b - a);
} else {
nums[i - 1] = 0;
}
}
var totalDist = 0;
for(i = 1; i < ylen; i++) {
totalDist += nums[i - 1];
}
for(i = 1; i < ylen; i++) {
if(nums[i - 1] === 0) {
nums[i - 1] = 1;
} else {
nums[i - 1] = Math.round(totalDist / nums[i - 1]);
}
}
return nums;
};
var highlyComposites = [1, 2, 4, 6, 12, 24, 36, 48, 60, 120, 180, 240, 360, 720, 840, 1260];
var MIN_RESOLUTION = highlyComposites[9];
var MAX_RESOLUTION = highlyComposites[13];
proto.estimateScale = function(resSrc, axis) {
var nums = (axis === 0) ?
this.calcXnums(resSrc) :
this.calcYnums(resSrc);
var resDst = 1 + arrayLCM(nums);
while(resDst < MIN_RESOLUTION) {
resDst *= 2;
}
while(resDst > MAX_RESOLUTION) {
resDst--;
resDst /= smallestDivisor(resDst);
resDst++;
if(resDst < MIN_RESOLUTION) {
// resDst = MIN_RESOLUTION; // option 1: use min resolution
resDst = MAX_RESOLUTION; // option 2: use max resolution
}
}
var scale = Math.round(resDst / resSrc);
return (scale > 1) ? scale : 1;
};
// based on Mikola Lysenko's ndarray-homography
// see https://github.com/scijs/ndarray-homography
function fnHomography(out, inp, X) {
var w = X[8] + X[2] * inp[0] + X[5] * inp[1];
out[0] = (X[6] + X[0] * inp[0] + X[3] * inp[1]) / w;
out[1] = (X[7] + X[1] * inp[0] + X[4] * inp[1]) / w;
return out;
}
function homography(dest, src, X) {
warp(dest, src, fnHomography, X);
return dest;
}
// based on Mikola Lysenko's ndarray-warp
// see https://github.com/scijs/ndarray-warp
function warp(dest, src, func, X) {
var warped = [0, 0];
var ni = dest.shape[0];
var nj = dest.shape[1];
for(var i = 0; i < ni; i++) {
for(var j = 0; j < nj; j++) {
func(warped, [i, j], X);
dest.set(i, j, ndarrayInterp2d(src, warped[0], warped[1]));
}
}
return dest;
}
proto.refineCoords = function(coords) {
var scaleW = this.dataScaleX;
var scaleH = this.dataScaleY;
var width = coords[0].shape[0];
var height = coords[0].shape[1];
var newWidth = Math.floor(coords[0].shape[0] * scaleW + 1) | 0;
var newHeight = Math.floor(coords[0].shape[1] * scaleH + 1) | 0;
// Pad coords by +1
var padWidth = 1 + width + 1;
var padHeight = 1 + height + 1;
var padImg = ndarray(new Float32Array(padWidth * padHeight), [padWidth, padHeight]);
var X = [
1 / scaleW, 0, 0,
0, 1 / scaleH, 0,
0, 0, 1
];
for(var i = 0; i < coords.length; ++i) {
this.surface.padField(padImg, coords[i]);
var scaledImg = ndarray(new Float32Array(newWidth * newHeight), [newWidth, newHeight]);
homography(scaledImg, padImg, X);
coords[i] = scaledImg;
}
};
function insertIfNewLevel(arr, newValue) {
var found = false;
for(var k = 0; k < arr.length; k++) {
if(newValue === arr[k]) {
found = true;
break;
}
}
if(found === false) arr.push(newValue);
}
proto.setContourLevels = function() {
var newLevels = [[], [], []];
var useNewLevels = [false, false, false];
var needsUpdate = false;
var i, j, value;
for(i = 0; i < 3; ++i) {
if(this.showContour[i]) {
needsUpdate = true;
if(
this.contourSize[i] > 0 &&
this.contourStart[i] !== null &&
this.contourEnd[i] !== null &&
this.contourEnd[i] > this.contourStart[i]
) {
useNewLevels[i] = true;
for(j = this.contourStart[i]; j < this.contourEnd[i]; j += this.contourSize[i]) {
value = j * this.scene.dataScale[i];
insertIfNewLevel(newLevels[i], value);
}
}
}
}
if(needsUpdate) {
var allLevels = [[], [], []];
for(i = 0; i < 3; ++i) {
if(this.showContour[i]) {
allLevels[i] = useNewLevels[i] ? newLevels[i] : this.scene.contourLevels[i];
}
}
this.surface.update({ levels: allLevels });
}
};
proto.update = function(data) {
var scene = this.scene;
var sceneLayout = scene.fullSceneLayout;
var surface = this.surface;
var colormap = parseColorScale(data);
var scaleFactor = scene.dataScale;
var xlen = data.z[0].length;
var ylen = data._ylength;
var contourLevels = scene.contourLevels;
// Save data
this.data = data;
/*
* Fill and transpose zdata.
* Consistent with 'heatmap' and 'contour', plotly 'surface'
* 'z' are such that sub-arrays correspond to y-coords
* and that the sub-array entries correspond to a x-coords,
* which is the transpose of 'gl-surface-plot'.
*/
var i, j, k, v;
var rawCoords = [];
for(i = 0; i < 3; i++) {
rawCoords[i] = [];
for(j = 0; j < xlen; j++) {
rawCoords[i][j] = [];
/*
for(k = 0; k < ylen; k++) {
rawCoords[i][j][k] = undefined;
}
*/
}
}
// coords x, y & z
for(j = 0; j < xlen; j++) {
for(k = 0; k < ylen; k++) {
rawCoords[0][j][k] = this.getXat(j, k, data.xcalendar, sceneLayout.xaxis);
rawCoords[1][j][k] = this.getYat(j, k, data.ycalendar, sceneLayout.yaxis);
rawCoords[2][j][k] = this.getZat(j, k, data.zcalendar, sceneLayout.zaxis);
}
}
if(data.connectgaps) {
data._emptypoints = findEmpties(rawCoords[2]);
interp2d(rawCoords[2], data._emptypoints);
data._interpolatedZ = [];
for(j = 0; j < xlen; j++) {
data._interpolatedZ[j] = [];
for(k = 0; k < ylen; k++) {
data._interpolatedZ[j][k] = rawCoords[2][j][k];
}
}
}
// Note: log axes are not defined in surfaces yet.
// but they could be defined here...
for(i = 0; i < 3; i++) {
for(j = 0; j < xlen; j++) {
for(k = 0; k < ylen; k++) {
v = rawCoords[i][j][k];
if(v === null || v === undefined) {
rawCoords[i][j][k] = NaN;
} else {
v = rawCoords[i][j][k] *= scaleFactor[i];
}
}
}
}
for(i = 0; i < 3; i++) {
for(j = 0; j < xlen; j++) {
for(k = 0; k < ylen; k++) {
v = rawCoords[i][j][k];
if(v !== null && v !== undefined) {
if(this.minValues[i] > v) {
this.minValues[i] = v;
}
if(this.maxValues[i] < v) {
this.maxValues[i] = v;
}
}
}
}
}
for(i = 0; i < 3; i++) {
this.objectOffset[i] = 0.5 * (this.minValues[i] + this.maxValues[i]);
}
for(i = 0; i < 3; i++) {
for(j = 0; j < xlen; j++) {
for(k = 0; k < ylen; k++) {
v = rawCoords[i][j][k];
if(v !== null && v !== undefined) {
rawCoords[i][j][k] -= this.objectOffset[i];
}
}
}
}
// convert processed raw data to Float32 matrices
var coords = [
ndarray(new Float32Array(xlen * ylen), [xlen, ylen]),
ndarray(new Float32Array(xlen * ylen), [xlen, ylen]),
ndarray(new Float32Array(xlen * ylen), [xlen, ylen])
];
for(i = 0; i < 3; i++) {
for(j = 0; j < xlen; j++) {
for(k = 0; k < ylen; k++) {
coords[i].set(j, k, rawCoords[i][j][k]);
}
}
}
rawCoords = []; // free memory
var params = {
colormap: colormap,
levels: [[], [], []],
showContour: [true, true, true],
showSurface: !data.hidesurface,
contourProject: [
[false, false, false],
[false, false, false],
[false, false, false]
],
contourWidth: [1, 1, 1],
contourColor: [[1, 1, 1, 1], [1, 1, 1, 1], [1, 1, 1, 1]],
contourTint: [1, 1, 1],
dynamicColor: [[1, 1, 1, 1], [1, 1, 1, 1], [1, 1, 1, 1]],
dynamicWidth: [1, 1, 1],
dynamicTint: [1, 1, 1],
opacityscale: data.opacityscale,
opacity: data.opacity
};
var cOpts = extractOpts(data);
params.intensityBounds = [cOpts.min, cOpts.max];
// Refine surface color if necessary
if(data.surfacecolor) {
var intensity = ndarray(new Float32Array(xlen * ylen), [xlen, ylen]);
for(j = 0; j < xlen; j++) {
for(k = 0; k < ylen; k++) {
intensity.set(j, k, data.surfacecolor[k][j]);
}
}
coords.push(intensity);
} else {
// when 'z' is used as 'intensity',
// we must scale its value
params.intensityBounds[0] *= scaleFactor[2];
params.intensityBounds[1] *= scaleFactor[2];
}
if(MAX_RESOLUTION < coords[0].shape[0] ||
MAX_RESOLUTION < coords[0].shape[1]) {
this.refineData = false;
}
if(this.refineData === true) {
this.dataScaleX = this.estimateScale(coords[0].shape[0], 0);
this.dataScaleY = this.estimateScale(coords[0].shape[1], 1);
if(this.dataScaleX !== 1 || this.dataScaleY !== 1) {
this.refineCoords(coords);
}
}
if(data.surfacecolor) {
params.intensity = coords.pop();
}
var highlightEnable = [true, true, true];
var axis = ['x', 'y', 'z'];
for(i = 0; i < 3; ++i) {
var contourParams = data.contours[axis[i]];
highlightEnable[i] = contourParams.highlight;
params.showContour[i] = contourParams.show || contourParams.highlight;
if(!params.showContour[i]) continue;
params.contourProject[i] = [
contourParams.project.x,
contourParams.project.y,
contourParams.project.z
];
if(contourParams.show) {
this.showContour[i] = true;
params.levels[i] = contourLevels[i];
surface.highlightColor[i] = params.contourColor[i] = str2RgbaArray(contourParams.color);
if(contourParams.usecolormap) {
surface.highlightTint[i] = params.contourTint[i] = 0;
} else {
surface.highlightTint[i] = params.contourTint[i] = 1;
}
params.contourWidth[i] = contourParams.width;
this.contourStart[i] = contourParams.start;
this.contourEnd[i] = contourParams.end;
this.contourSize[i] = contourParams.size;
} else {
this.showContour[i] = false;
this.contourStart[i] = null;
this.contourEnd[i] = null;
this.contourSize[i] = 0;
}
if(contourParams.highlight) {
params.dynamicColor[i] = str2RgbaArray(contourParams.highlightcolor);
params.dynamicWidth[i] = contourParams.highlightwidth;
}
}
// see https://github.com/plotly/plotly.js/issues/940
if(isColormapCircular(colormap)) {
params.vertexColor = true;
}
params.objectOffset = this.objectOffset;
params.coords = coords;
surface.update(params);
surface.visible = data.visible;
surface.enableDynamic = highlightEnable;
surface.enableHighlight = highlightEnable;
surface.snapToData = true;
if('lighting' in data) {
surface.ambientLight = data.lighting.ambient;
surface.diffuseLight = data.lighting.diffuse;
surface.specularLight = data.lighting.specular;
surface.roughness = data.lighting.roughness;
surface.fresnel = data.lighting.fresnel;
}
if('lightposition' in data) {
surface.lightPosition = [data.lightposition.x, data.lightposition.y, data.lightposition.z];
}
};
proto.dispose = function() {
this.scene.glplot.remove(this.surface);
this.surface.dispose();
};
function createSurfaceTrace(scene, data) {
var gl = scene.glplot.gl;
var surface = createSurface({ gl: gl });
var result = new SurfaceTrace(scene, surface, data.uid);
surface._trace = result;
result.update(data);
scene.glplot.add(surface);
return result;
}
module.exports = createSurfaceTrace;
},{"../../../stackgl_modules":1119,"../../components/colorscale":378,"../../lib":503,"../../lib/gl_format_color":499,"../../lib/str2rgbarray":528,"../heatmap/find_empties":798,"../heatmap/interp2d":801}],1059:[function(_dereq_,module,exports){
'use strict';
var Registry = _dereq_('../../registry');
var Lib = _dereq_('../../lib');
var colorscaleDefaults = _dereq_('../../components/colorscale/defaults');
var attributes = _dereq_('./attributes');
var MIN = 0.1; // Note: often we don't want the data cube to be disappeared
function createWave(n, minOpacity) {
var arr = [];
var steps = 32; // Max: 256
for(var i = 0; i < steps; i++) {
var u = i / (steps - 1);
var v = minOpacity + (1 - minOpacity) * (1 - Math.pow(Math.sin(n * u * Math.PI), 2));
arr.push([
u,
Math.max(0, Math.min(1, v))
]);
}
return arr;
}
function isValidScaleArray(scl) {
var highestVal = 0;
if(!Array.isArray(scl) || scl.length < 2) return false;
if(!scl[0] || !scl[scl.length - 1]) return false;
if(+scl[0][0] !== 0 || +scl[scl.length - 1][0] !== 1) return false;
for(var i = 0; i < scl.length; i++) {
var si = scl[i];
if(si.length !== 2 || +si[0] < highestVal) {
return false;
}
highestVal = +si[0];
}
return true;
}
function supplyDefaults(traceIn, traceOut, defaultColor, layout) {
var i, j;
function coerce(attr, dflt) {
return Lib.coerce(traceIn, traceOut, attributes, attr, dflt);
}
var x = coerce('x');
var y = coerce('y');
var z = coerce('z');
if(!z || !z.length ||
(x ? (x.length < 1) : false) ||
(y ? (y.length < 1) : false)
) {
traceOut.visible = false;
return;
}
traceOut._xlength = (Array.isArray(x) && Lib.isArrayOrTypedArray(x[0])) ? z.length : z[0].length;
traceOut._ylength = z.length;
var handleCalendarDefaults = Registry.getComponentMethod('calendars', 'handleTraceDefaults');
handleCalendarDefaults(traceIn, traceOut, ['x', 'y', 'z'], layout);
coerce('text');
coerce('hovertext');
coerce('hovertemplate');
coerce('xhoverformat');
coerce('yhoverformat');
coerce('zhoverformat');
// Coerce remaining properties
[
'lighting.ambient',
'lighting.diffuse',
'lighting.specular',
'lighting.roughness',
'lighting.fresnel',
'lightposition.x',
'lightposition.y',
'lightposition.z',
'hidesurface',
'connectgaps',
'opacity'
].forEach(function(x) { coerce(x); });
var surfaceColor = coerce('surfacecolor');
var dims = ['x', 'y', 'z'];
for(i = 0; i < 3; ++i) {
var contourDim = 'contours.' + dims[i];
var show = coerce(contourDim + '.show');
var highlight = coerce(contourDim + '.highlight');
if(show || highlight) {
for(j = 0; j < 3; ++j) {
coerce(contourDim + '.project.' + dims[j]);
}
}
if(show) {
coerce(contourDim + '.color');
coerce(contourDim + '.width');
coerce(contourDim + '.usecolormap');
}
if(highlight) {
coerce(contourDim + '.highlightcolor');
coerce(contourDim + '.highlightwidth');
}
coerce(contourDim + '.start');
coerce(contourDim + '.end');
coerce(contourDim + '.size');
}
// backward compatibility block
if(!surfaceColor) {
mapLegacy(traceIn, 'zmin', 'cmin');
mapLegacy(traceIn, 'zmax', 'cmax');
mapLegacy(traceIn, 'zauto', 'cauto');
}
// TODO if contours.?.usecolormap are false and hidesurface is true
// the colorbar shouldn't be shown by default
colorscaleDefaults(
traceIn, traceOut, layout, coerce, {prefix: '', cLetter: 'c'}
);
opacityscaleDefaults(traceIn, traceOut, layout, coerce);
// disable 1D transforms - currently surface does NOT support column data like heatmap does
// you can use mesh3d for this use case, but not surface
traceOut._length = null;
}
function opacityscaleDefaults(traceIn, traceOut, layout, coerce) {
var opacityscale = coerce('opacityscale');
if(opacityscale === 'max') {
traceOut.opacityscale = [[0, MIN], [1, 1]];
} else if(opacityscale === 'min') {
traceOut.opacityscale = [[0, 1], [1, MIN]];
} else if(opacityscale === 'extremes') {
traceOut.opacityscale = createWave(1, MIN);
} else if(!isValidScaleArray(opacityscale)) {
traceOut.opacityscale = undefined;
}
}
function mapLegacy(traceIn, oldAttr, newAttr) {
if(oldAttr in traceIn && !(newAttr in traceIn)) {
traceIn[newAttr] = traceIn[oldAttr];
}
}
module.exports = {
supplyDefaults: supplyDefaults,
opacityscaleDefaults: opacityscaleDefaults
};
},{"../../components/colorscale/defaults":376,"../../lib":503,"../../registry":638,"./attributes":1056}],1060:[function(_dereq_,module,exports){
'use strict';
module.exports = {
attributes: _dereq_('./attributes'),
supplyDefaults: _dereq_('./defaults').supplyDefaults,
colorbar: {
min: 'cmin',
max: 'cmax'
},
calc: _dereq_('./calc'),
plot: _dereq_('./convert'),
moduleType: 'trace',
name: 'surface',
basePlotModule: _dereq_('../../plots/gl3d'),
categories: ['gl3d', '2dMap', 'showLegend'],
meta: {
}
};
},{"../../plots/gl3d":598,"./attributes":1056,"./calc":1057,"./convert":1058,"./defaults":1059}],1061:[function(_dereq_,module,exports){
'use strict';
var annAttrs = _dereq_('../../components/annotations/attributes');
var extendFlat = _dereq_('../../lib/extend').extendFlat;
var overrideAll = _dereq_('../../plot_api/edit_types').overrideAll;
var fontAttrs = _dereq_('../../plots/font_attributes');
var domainAttrs = _dereq_('../../plots/domain').attributes;
var descriptionOnlyNumbers = _dereq_('../../plots/cartesian/axis_format_attributes').descriptionOnlyNumbers;
var attrs = module.exports = overrideAll({
domain: domainAttrs({name: 'table', trace: true}),
columnwidth: {
valType: 'number',
arrayOk: true,
dflt: null,
},
columnorder: {
valType: 'data_array',
},
header: {
values: {
valType: 'data_array',
dflt: [],
},
format: {
valType: 'data_array',
dflt: [],
description: descriptionOnlyNumbers('cell value')
},
prefix: {
valType: 'string',
arrayOk: true,
dflt: null,
},
suffix: {
valType: 'string',
arrayOk: true,
dflt: null,
},
height: {
valType: 'number',
dflt: 28,
},
align: extendFlat({}, annAttrs.align, {arrayOk: true}),
line: {
width: {
valType: 'number',
arrayOk: true,
dflt: 1,
},
color: {
valType: 'color',
arrayOk: true,
dflt: 'grey',
}
},
fill: {
color: {
valType: 'color',
arrayOk: true,
dflt: 'white',
}
},
font: extendFlat({}, fontAttrs({arrayOk: true}))
},
cells: {
values: {
valType: 'data_array',
dflt: [],
},
format: {
valType: 'data_array',
dflt: [],
description: descriptionOnlyNumbers('cell value')
},
prefix: {
valType: 'string',
arrayOk: true,
dflt: null,
},
suffix: {
valType: 'string',
arrayOk: true,
dflt: null,
},
height: {
valType: 'number',
dflt: 20,
},
align: extendFlat({}, annAttrs.align, {arrayOk: true}),
line: {
width: {
valType: 'number',
arrayOk: true,
dflt: 1,
},
color: {
valType: 'color',
arrayOk: true,
dflt: 'grey',
}
},
fill: {
color: {
valType: 'color',
arrayOk: true,
dflt: 'white',
}
},
font: extendFlat({}, fontAttrs({arrayOk: true}))
}
}, 'calc', 'from-root');
attrs.transforms = undefined;
},{"../../components/annotations/attributes":349,"../../lib/extend":493,"../../plot_api/edit_types":536,"../../plots/cartesian/axis_format_attributes":557,"../../plots/domain":584,"../../plots/font_attributes":585}],1062:[function(_dereq_,module,exports){
'use strict';
var getModuleCalcData = _dereq_('../../plots/get_data').getModuleCalcData;
var tablePlot = _dereq_('./plot');
var TABLE = 'table';
exports.name = TABLE;
exports.plot = function(gd) {
var calcData = getModuleCalcData(gd.calcdata, TABLE)[0];
if(calcData.length) tablePlot(gd, calcData);
};
exports.clean = function(newFullData, newFullLayout, oldFullData, oldFullLayout) {
var hadTable = (oldFullLayout._has && oldFullLayout._has(TABLE));
var hasTable = (newFullLayout._has && newFullLayout._has(TABLE));
if(hadTable && !hasTable) {
oldFullLayout._paperdiv.selectAll('.table').remove();
}
};
},{"../../plots/get_data":593,"./plot":1069}],1063:[function(_dereq_,module,exports){
'use strict';
var wrap = _dereq_('../../lib/gup').wrap;
module.exports = function calc() {
// we don't actually need to include the trace here, since that will be added
// by Plots.doCalcdata, and that's all we actually need later.
return wrap({});
};
},{"../../lib/gup":500}],1064:[function(_dereq_,module,exports){
'use strict';
module.exports = {
cellPad: 8,
columnExtentOffset: 10,
columnTitleOffset: 28,
emptyHeaderHeight: 16,
latexCheck: /^\$.*\$$/,
goldenRatio: 1.618,
lineBreaker: '
',
maxDimensionCount: 60,
overdrag: 45,
releaseTransitionDuration: 120,
releaseTransitionEase: 'cubic-out',
scrollbarCaptureWidth: 18,
scrollbarHideDelay: 1000,
scrollbarHideDuration: 1000,
scrollbarOffset: 5,
scrollbarWidth: 8,
transitionDuration: 100,
transitionEase: 'cubic-out',
uplift: 5,
wrapSpacer: ' ',
wrapSplitCharacter: ' ',
cn: {
// general class names
table: 'table',
tableControlView: 'table-control-view',
scrollBackground: 'scroll-background',
yColumn: 'y-column',
columnBlock: 'column-block',
scrollAreaClip: 'scroll-area-clip',
scrollAreaClipRect: 'scroll-area-clip-rect',
columnBoundary: 'column-boundary',
columnBoundaryClippath: 'column-boundary-clippath',
columnBoundaryRect: 'column-boundary-rect',
columnCells: 'column-cells',
columnCell: 'column-cell',
cellRect: 'cell-rect',
cellText: 'cell-text',
cellTextHolder: 'cell-text-holder',
// scroll related class names
scrollbarKit: 'scrollbar-kit',
scrollbar: 'scrollbar',
scrollbarSlider: 'scrollbar-slider',
scrollbarGlyph: 'scrollbar-glyph',
scrollbarCaptureZone: 'scrollbar-capture-zone'
}
};
},{}],1065:[function(_dereq_,module,exports){
'use strict';
var c = _dereq_('./constants');
var extendFlat = _dereq_('../../lib/extend').extendFlat;
var isNumeric = _dereq_('fast-isnumeric');
// pure functions, don't alter but passes on `gd` and parts of `trace` without deep copying
module.exports = function calc(gd, trace) {
var cellsValues = squareStringMatrix(trace.cells.values);
var slicer = function(a) {
return a.slice(trace.header.values.length, a.length);
};
var headerValuesIn = squareStringMatrix(trace.header.values);
if(headerValuesIn.length && !headerValuesIn[0].length) {
headerValuesIn[0] = [''];
headerValuesIn = squareStringMatrix(headerValuesIn);
}
var headerValues = headerValuesIn
.concat(slicer(cellsValues).map(function() {
return emptyStrings((headerValuesIn[0] || ['']).length);
}));
var domain = trace.domain;
var groupWidth = Math.floor(gd._fullLayout._size.w * (domain.x[1] - domain.x[0]));
var groupHeight = Math.floor(gd._fullLayout._size.h * (domain.y[1] - domain.y[0]));
var headerRowHeights = trace.header.values.length ?
headerValues[0].map(function() { return trace.header.height; }) :
[c.emptyHeaderHeight];
var rowHeights = cellsValues.length ? cellsValues[0].map(function() { return trace.cells.height; }) : [];
var headerHeight = headerRowHeights.reduce(sum, 0);
var scrollHeight = groupHeight - headerHeight;
var minimumFillHeight = scrollHeight + c.uplift;
var anchorToRowBlock = makeAnchorToRowBlock(rowHeights, minimumFillHeight);
var anchorToHeaderRowBlock = makeAnchorToRowBlock(headerRowHeights, headerHeight);
var headerRowBlocks = makeRowBlock(anchorToHeaderRowBlock, []);
var rowBlocks = makeRowBlock(anchorToRowBlock, headerRowBlocks);
var uniqueKeys = {};
var columnOrder = trace._fullInput.columnorder.concat(slicer(cellsValues.map(function(d, i) {return i;})));
var columnWidths = headerValues.map(function(d, i) {
var value = Array.isArray(trace.columnwidth) ?
trace.columnwidth[Math.min(i, trace.columnwidth.length - 1)] :
trace.columnwidth;
return isNumeric(value) ? Number(value) : 1;
});
var totalColumnWidths = columnWidths.reduce(sum, 0);
// fit columns in the available vertical space as there's no vertical scrolling now
columnWidths = columnWidths.map(function(d) { return d / totalColumnWidths * groupWidth; });
var maxLineWidth = Math.max(arrayMax(trace.header.line.width), arrayMax(trace.cells.line.width));
var calcdata = {
// include staticPlot in the key so if it changes we delete and redraw
key: trace.uid + gd._context.staticPlot,
translateX: domain.x[0] * gd._fullLayout._size.w,
translateY: gd._fullLayout._size.h * (1 - domain.y[1]),
size: gd._fullLayout._size,
width: groupWidth,
maxLineWidth: maxLineWidth,
height: groupHeight,
columnOrder: columnOrder, // will be mutated on column move, todo use in callback
groupHeight: groupHeight,
rowBlocks: rowBlocks,
headerRowBlocks: headerRowBlocks,
scrollY: 0, // will be mutated on scroll
cells: extendFlat({}, trace.cells, {values: cellsValues}),
headerCells: extendFlat({}, trace.header, {values: headerValues}),
gdColumns: headerValues.map(function(d) {return d[0];}),
gdColumnsOriginalOrder: headerValues.map(function(d) {return d[0];}),
prevPages: [0, 0],
scrollbarState: {scrollbarScrollInProgress: false},
columns: headerValues.map(function(label, i) {
var foundKey = uniqueKeys[label];
uniqueKeys[label] = (foundKey || 0) + 1;
var key = label + '__' + uniqueKeys[label];
return {
key: key,
label: label,
specIndex: i,
xIndex: columnOrder[i],
xScale: xScale,
x: undefined, // initialized below
calcdata: undefined, // initialized below
columnWidth: columnWidths[i]
};
})
};
calcdata.columns.forEach(function(col) {
col.calcdata = calcdata;
col.x = xScale(col);
});
return calcdata;
};
function arrayMax(maybeArray) {
if(Array.isArray(maybeArray)) {
var max = 0;
for(var i = 0; i < maybeArray.length; i++) {
max = Math.max(max, arrayMax(maybeArray[i]));
}
return max;
}
return maybeArray;
}
function sum(a, b) { return a + b; }
// fill matrix in place to equal lengths
// and ensure it's uniformly 2D
function squareStringMatrix(matrixIn) {
var matrix = matrixIn.slice();
var minLen = Infinity;
var maxLen = 0;
var i;
for(i = 0; i < matrix.length; i++) {
if(!Array.isArray(matrix[i])) matrix[i] = [matrix[i]];
minLen = Math.min(minLen, matrix[i].length);
maxLen = Math.max(maxLen, matrix[i].length);
}
if(minLen !== maxLen) {
for(i = 0; i < matrix.length; i++) {
var padLen = maxLen - matrix[i].length;
if(padLen) matrix[i] = matrix[i].concat(emptyStrings(padLen));
}
}
return matrix;
}
function emptyStrings(len) {
var padArray = new Array(len);
for(var j = 0; j < len; j++) padArray[j] = '';
return padArray;
}
function xScale(d) {
return d.calcdata.columns.reduce(function(prev, next) {
return next.xIndex < d.xIndex ? prev + next.columnWidth : prev;
}, 0);
}
function makeRowBlock(anchorToRowBlock, auxiliary) {
var blockAnchorKeys = Object.keys(anchorToRowBlock);
return blockAnchorKeys.map(function(k) {return extendFlat({}, anchorToRowBlock[k], {auxiliaryBlocks: auxiliary});});
}
function makeAnchorToRowBlock(rowHeights, minimumFillHeight) {
var anchorToRowBlock = {};
var currentRowHeight;
var currentAnchor = 0;
var currentBlockHeight = 0;
var currentBlock = makeIdentity();
var currentFirstRowIndex = 0;
var blockCounter = 0;
for(var i = 0; i < rowHeights.length; i++) {
currentRowHeight = rowHeights[i];
currentBlock.rows.push({
rowIndex: i,
rowHeight: currentRowHeight
});
currentBlockHeight += currentRowHeight;
if(currentBlockHeight >= minimumFillHeight || i === rowHeights.length - 1) {
anchorToRowBlock[currentAnchor] = currentBlock;
currentBlock.key = blockCounter++;
currentBlock.firstRowIndex = currentFirstRowIndex;
currentBlock.lastRowIndex = i;
currentBlock = makeIdentity();
currentAnchor += currentBlockHeight;
currentFirstRowIndex = i + 1;
currentBlockHeight = 0;
}
}
return anchorToRowBlock;
}
function makeIdentity() {
return {
firstRowIndex: null,
lastRowIndex: null,
rows: []
};
}
},{"../../lib/extend":493,"./constants":1064,"fast-isnumeric":190}],1066:[function(_dereq_,module,exports){
'use strict';
var extendFlat = _dereq_('../../lib/extend').extendFlat;
// pure functions, don't alter but passes on `gd` and parts of `trace` without deep copying
exports.splitToPanels = function(d) {
var prevPages = [0, 0];
var headerPanel = extendFlat({}, d, {
key: 'header',
type: 'header',
page: 0,
prevPages: prevPages,
currentRepaint: [null, null],
dragHandle: true,
values: d.calcdata.headerCells.values[d.specIndex],
rowBlocks: d.calcdata.headerRowBlocks,
calcdata: extendFlat({}, d.calcdata, {cells: d.calcdata.headerCells})
});
var revolverPanel1 = extendFlat({}, d, {
key: 'cells1',
type: 'cells',
page: 0,
prevPages: prevPages,
currentRepaint: [null, null],
dragHandle: false,
values: d.calcdata.cells.values[d.specIndex],
rowBlocks: d.calcdata.rowBlocks
});
var revolverPanel2 = extendFlat({}, d, {
key: 'cells2',
type: 'cells',
page: 1,
prevPages: prevPages,
currentRepaint: [null, null],
dragHandle: false,
values: d.calcdata.cells.values[d.specIndex],
rowBlocks: d.calcdata.rowBlocks
});
// order due to SVG using painter's algo:
return [revolverPanel1, revolverPanel2, headerPanel];
};
exports.splitToCells = function(d) {
var fromTo = rowFromTo(d);
return (d.values || []).slice(fromTo[0], fromTo[1]).map(function(v, i) {
// By keeping identical key, a DOM node removal, creation and addition is spared, important when visible
// grid has a lot of elements (quadratic with xcol/ycol count).
// But it has to be busted when `svgUtil.convertToTspans` is used as it reshapes cell subtrees asynchronously,
// and by that time the user may have scrolled away, resulting in stale overwrites. The real solution will be
// to turn `svgUtil.convertToTspans` into a cancelable request, in which case no key busting is needed.
var buster = (typeof v === 'string') && v.match(/[<$&> ]/) ? '_keybuster_' + Math.random() : '';
return {
// keyWithinBlock: /*fromTo[0] + */i, // optimized future version - no busting
// keyWithinBlock: fromTo[0] + i, // initial always-unoptimized version - janky scrolling with 5+ columns
keyWithinBlock: i + buster, // current compromise: regular content is very fast; async content is possible
key: fromTo[0] + i,
column: d,
calcdata: d.calcdata,
page: d.page,
rowBlocks: d.rowBlocks,
value: v
};
});
};
function rowFromTo(d) {
var rowBlock = d.rowBlocks[d.page];
// fixme rowBlock truthiness check is due to ugly hack of placing 2nd panel as d.page = -1
var rowFrom = rowBlock ? rowBlock.rows[0].rowIndex : 0;
var rowTo = rowBlock ? rowFrom + rowBlock.rows.length : 0;
return [rowFrom, rowTo];
}
},{"../../lib/extend":493}],1067:[function(_dereq_,module,exports){
'use strict';
var Lib = _dereq_('../../lib');
var attributes = _dereq_('./attributes');
var handleDomainDefaults = _dereq_('../../plots/domain').defaults;
function defaultColumnOrder(traceOut, coerce) {
var specifiedColumnOrder = traceOut.columnorder || [];
var commonLength = traceOut.header.values.length;
var truncated = specifiedColumnOrder.slice(0, commonLength);
var sorted = truncated.slice().sort(function(a, b) {return a - b;});
var oneStepped = truncated.map(function(d) {return sorted.indexOf(d);});
for(var i = oneStepped.length; i < commonLength; i++) {
oneStepped.push(i);
}
coerce('columnorder', oneStepped);
}
module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout) {
function coerce(attr, dflt) {
return Lib.coerce(traceIn, traceOut, attributes, attr, dflt);
}
handleDomainDefaults(traceOut, layout, coerce);
coerce('columnwidth');
coerce('header.values');
coerce('header.format');
coerce('header.align');
coerce('header.prefix');
coerce('header.suffix');
coerce('header.height');
coerce('header.line.width');
coerce('header.line.color');
coerce('header.fill.color');
Lib.coerceFont(coerce, 'header.font', Lib.extendFlat({}, layout.font));
defaultColumnOrder(traceOut, coerce);
coerce('cells.values');
coerce('cells.format');
coerce('cells.align');
coerce('cells.prefix');
coerce('cells.suffix');
coerce('cells.height');
coerce('cells.line.width');
coerce('cells.line.color');
coerce('cells.fill.color');
Lib.coerceFont(coerce, 'cells.font', Lib.extendFlat({}, layout.font));
// disable 1D transforms
traceOut._length = null;
};
},{"../../lib":503,"../../plots/domain":584,"./attributes":1061}],1068:[function(_dereq_,module,exports){
'use strict';
module.exports = {
attributes: _dereq_('./attributes'),
supplyDefaults: _dereq_('./defaults'),
calc: _dereq_('./calc'),
plot: _dereq_('./plot'),
moduleType: 'trace',
name: 'table',
basePlotModule: _dereq_('./base_plot'),
categories: ['noOpacity'],
meta: {
}
};
},{"./attributes":1061,"./base_plot":1062,"./calc":1063,"./defaults":1067,"./plot":1069}],1069:[function(_dereq_,module,exports){
'use strict';
var c = _dereq_('./constants');
var d3 = _dereq_('@plotly/d3');
var Lib = _dereq_('../../lib');
var numberFormat = Lib.numberFormat;
var gup = _dereq_('../../lib/gup');
var Drawing = _dereq_('../../components/drawing');
var svgUtil = _dereq_('../../lib/svg_text_utils');
var raiseToTop = _dereq_('../../lib').raiseToTop;
var strTranslate = _dereq_('../../lib').strTranslate;
var cancelEeaseColumn = _dereq_('../../lib').cancelTransition;
var prepareData = _dereq_('./data_preparation_helper');
var splitData = _dereq_('./data_split_helpers');
var Color = _dereq_('../../components/color');
module.exports = function plot(gd, wrappedTraceHolders) {
var dynamic = !gd._context.staticPlot;
var table = gd._fullLayout._paper.selectAll('.' + c.cn.table)
.data(wrappedTraceHolders.map(function(wrappedTraceHolder) {
var traceHolder = gup.unwrap(wrappedTraceHolder);
var trace = traceHolder.trace;
return prepareData(gd, trace);
}), gup.keyFun);
table.exit().remove();
table.enter()
.append('g')
.classed(c.cn.table, true)
.attr('overflow', 'visible')
.style('box-sizing', 'content-box')
.style('position', 'absolute')
.style('left', 0)
.style('overflow', 'visible')
.style('shape-rendering', 'crispEdges')
.style('pointer-events', 'all');
table
.attr('width', function(d) {return d.width + d.size.l + d.size.r;})
.attr('height', function(d) {return d.height + d.size.t + d.size.b;})
.attr('transform', function(d) {
return strTranslate(d.translateX, d.translateY);
});
var tableControlView = table.selectAll('.' + c.cn.tableControlView)
.data(gup.repeat, gup.keyFun);
var cvEnter = tableControlView.enter()
.append('g')
.classed(c.cn.tableControlView, true)
.style('box-sizing', 'content-box');
if(dynamic) {
var wheelEvent = 'onwheel' in document ? 'wheel' : 'mousewheel';
cvEnter
.on('mousemove', function(d) {
tableControlView
.filter(function(dd) {return d === dd;})
.call(renderScrollbarKit, gd);
})
.on(wheelEvent, function(d) {
if(d.scrollbarState.wheeling) return;
d.scrollbarState.wheeling = true;
var newY = d.scrollY + d3.event.deltaY;
var noChange = makeDragRow(gd, tableControlView, null, newY)(d);
if(!noChange) {
d3.event.stopPropagation();
d3.event.preventDefault();
}
d.scrollbarState.wheeling = false;
})
.call(renderScrollbarKit, gd, true);
}
tableControlView
.attr('transform', function(d) {return strTranslate(d.size.l, d.size.t);});
// scrollBackground merely ensures that mouse events are captured even on crazy fast scrollwheeling
// otherwise rendering glitches may occur
var scrollBackground = tableControlView.selectAll('.' + c.cn.scrollBackground)
.data(gup.repeat, gup.keyFun);
scrollBackground.enter()
.append('rect')
.classed(c.cn.scrollBackground, true)
.attr('fill', 'none');
scrollBackground
.attr('width', function(d) {return d.width;})
.attr('height', function(d) {return d.height;});
tableControlView.each(function(d) {
Drawing.setClipUrl(d3.select(this), scrollAreaBottomClipKey(gd, d), gd);
});
var yColumn = tableControlView.selectAll('.' + c.cn.yColumn)
.data(function(vm) {return vm.columns;}, gup.keyFun);
yColumn.enter()
.append('g')
.classed(c.cn.yColumn, true);
yColumn.exit().remove();
yColumn.attr('transform', function(d) {return strTranslate(d.x, 0);});
if(dynamic) {
yColumn.call(d3.behavior.drag()
.origin(function(d) {
var movedColumn = d3.select(this);
easeColumn(movedColumn, d, -c.uplift);
raiseToTop(this);
d.calcdata.columnDragInProgress = true;
renderScrollbarKit(tableControlView.filter(function(dd) {return d.calcdata.key === dd.key;}), gd);
return d;
})
.on('drag', function(d) {
var movedColumn = d3.select(this);
var getter = function(dd) {return (d === dd ? d3.event.x : dd.x) + dd.columnWidth / 2;};
d.x = Math.max(-c.overdrag, Math.min(d.calcdata.width + c.overdrag - d.columnWidth, d3.event.x));
var sortableColumns = flatData(yColumn).filter(function(dd) {return dd.calcdata.key === d.calcdata.key;});
var newOrder = sortableColumns.sort(function(a, b) {return getter(a) - getter(b);});
newOrder.forEach(function(dd, i) {
dd.xIndex = i;
dd.x = d === dd ? dd.x : dd.xScale(dd);
});
yColumn.filter(function(dd) {return d !== dd;})
.transition()
.ease(c.transitionEase)
.duration(c.transitionDuration)
.attr('transform', function(d) {return strTranslate(d.x, 0);});
movedColumn
.call(cancelEeaseColumn)
.attr('transform', strTranslate(d.x, -c.uplift));
})
.on('dragend', function(d) {
var movedColumn = d3.select(this);
var p = d.calcdata;
d.x = d.xScale(d);
d.calcdata.columnDragInProgress = false;
easeColumn(movedColumn, d, 0);
columnMoved(gd, p, p.columns.map(function(dd) {return dd.xIndex;}));
})
);
}
yColumn.each(function(d) {
Drawing.setClipUrl(d3.select(this), columnBoundaryClipKey(gd, d), gd);
});
var columnBlock = yColumn.selectAll('.' + c.cn.columnBlock)
.data(splitData.splitToPanels, gup.keyFun);
columnBlock.enter()
.append('g')
.classed(c.cn.columnBlock, true)
.attr('id', function(d) {return d.key;});
columnBlock
.style('cursor', function(d) {
return d.dragHandle ? 'ew-resize' : d.calcdata.scrollbarState.barWiggleRoom ? 'ns-resize' : 'default';
});
var headerColumnBlock = columnBlock.filter(headerBlock);
var cellsColumnBlock = columnBlock.filter(cellsBlock);
if(dynamic) {
cellsColumnBlock.call(d3.behavior.drag()
.origin(function(d) {
d3.event.stopPropagation();
return d;
})
.on('drag', makeDragRow(gd, tableControlView, -1))
.on('dragend', function() {
// fixme emit plotly notification
})
);
}
// initial rendering: header is rendered first, as it may may have async LaTeX (show header first)
// but blocks are _entered_ the way they are due to painter's algo (header on top)
renderColumnCellTree(gd, tableControlView, headerColumnBlock, columnBlock);
renderColumnCellTree(gd, tableControlView, cellsColumnBlock, columnBlock);
var scrollAreaClip = tableControlView.selectAll('.' + c.cn.scrollAreaClip)
.data(gup.repeat, gup.keyFun);
scrollAreaClip.enter()
.append('clipPath')
.classed(c.cn.scrollAreaClip, true)
.attr('id', function(d) {return scrollAreaBottomClipKey(gd, d);});
var scrollAreaClipRect = scrollAreaClip.selectAll('.' + c.cn.scrollAreaClipRect)
.data(gup.repeat, gup.keyFun);
scrollAreaClipRect.enter()
.append('rect')
.classed(c.cn.scrollAreaClipRect, true)
.attr('x', -c.overdrag)
.attr('y', -c.uplift)
.attr('fill', 'none');
scrollAreaClipRect
.attr('width', function(d) {return d.width + 2 * c.overdrag;})
.attr('height', function(d) {return d.height + c.uplift;});
var columnBoundary = yColumn.selectAll('.' + c.cn.columnBoundary)
.data(gup.repeat, gup.keyFun);
columnBoundary.enter()
.append('g')
.classed(c.cn.columnBoundary, true);
var columnBoundaryClippath = yColumn.selectAll('.' + c.cn.columnBoundaryClippath)
.data(gup.repeat, gup.keyFun);
// SVG spec doesn't mandate wrapping into a and doesn't seem to cause a speed difference
columnBoundaryClippath.enter()
.append('clipPath')
.classed(c.cn.columnBoundaryClippath, true);
columnBoundaryClippath
.attr('id', function(d) {return columnBoundaryClipKey(gd, d);});
var columnBoundaryRect = columnBoundaryClippath.selectAll('.' + c.cn.columnBoundaryRect)
.data(gup.repeat, gup.keyFun);
columnBoundaryRect.enter()
.append('rect')
.classed(c.cn.columnBoundaryRect, true)
.attr('fill', 'none');
columnBoundaryRect
.attr('width', function(d) { return d.columnWidth + 2 * roundHalfWidth(d); })
.attr('height', function(d) {return d.calcdata.height + 2 * roundHalfWidth(d) + c.uplift;})
.attr('x', function(d) { return -roundHalfWidth(d); })
.attr('y', function(d) { return -roundHalfWidth(d); });
updateBlockYPosition(null, cellsColumnBlock, tableControlView);
};
function roundHalfWidth(d) {
return Math.ceil(d.calcdata.maxLineWidth / 2);
}
function scrollAreaBottomClipKey(gd, d) {
return 'clip' + gd._fullLayout._uid + '_scrollAreaBottomClip_' + d.key;
}
function columnBoundaryClipKey(gd, d) {
return 'clip' + gd._fullLayout._uid + '_columnBoundaryClippath_' + d.calcdata.key + '_' + d.specIndex;
}
function flatData(selection) {
return [].concat.apply([], selection.map(function(g) {return g;}))
.map(function(g) {return g.__data__;});
}
function renderScrollbarKit(tableControlView, gd, bypassVisibleBar) {
function calcTotalHeight(d) {
var blocks = d.rowBlocks;
return firstRowAnchor(blocks, blocks.length - 1) + (blocks.length ? rowsHeight(blocks[blocks.length - 1], Infinity) : 1);
}
var scrollbarKit = tableControlView.selectAll('.' + c.cn.scrollbarKit)
.data(gup.repeat, gup.keyFun);
scrollbarKit.enter()
.append('g')
.classed(c.cn.scrollbarKit, true)
.style('shape-rendering', 'geometricPrecision');
scrollbarKit
.each(function(d) {
var s = d.scrollbarState;
s.totalHeight = calcTotalHeight(d);
s.scrollableAreaHeight = d.groupHeight - headerHeight(d);
s.currentlyVisibleHeight = Math.min(s.totalHeight, s.scrollableAreaHeight);
s.ratio = s.currentlyVisibleHeight / s.totalHeight;
s.barLength = Math.max(s.ratio * s.currentlyVisibleHeight, c.goldenRatio * c.scrollbarWidth);
s.barWiggleRoom = s.currentlyVisibleHeight - s.barLength;
s.wiggleRoom = Math.max(0, s.totalHeight - s.scrollableAreaHeight);
s.topY = s.barWiggleRoom === 0 ? 0 : (d.scrollY / s.wiggleRoom) * s.barWiggleRoom;
s.bottomY = s.topY + s.barLength;
s.dragMultiplier = s.wiggleRoom / s.barWiggleRoom;
})
.attr('transform', function(d) {
var xPosition = d.width + c.scrollbarWidth / 2 + c.scrollbarOffset;
return strTranslate(xPosition, headerHeight(d));
});
var scrollbar = scrollbarKit.selectAll('.' + c.cn.scrollbar)
.data(gup.repeat, gup.keyFun);
scrollbar.enter()
.append('g')
.classed(c.cn.scrollbar, true);
var scrollbarSlider = scrollbar.selectAll('.' + c.cn.scrollbarSlider)
.data(gup.repeat, gup.keyFun);
scrollbarSlider.enter()
.append('g')
.classed(c.cn.scrollbarSlider, true);
scrollbarSlider
.attr('transform', function(d) {
return strTranslate(0, d.scrollbarState.topY || 0);
});
var scrollbarGlyph = scrollbarSlider.selectAll('.' + c.cn.scrollbarGlyph)
.data(gup.repeat, gup.keyFun);
scrollbarGlyph.enter()
.append('line')
.classed(c.cn.scrollbarGlyph, true)
.attr('stroke', 'black')
.attr('stroke-width', c.scrollbarWidth)
.attr('stroke-linecap', 'round')
.attr('y1', c.scrollbarWidth / 2);
scrollbarGlyph
.attr('y2', function(d) {
return d.scrollbarState.barLength - c.scrollbarWidth / 2;
})
.attr('stroke-opacity', function(d) {
return d.columnDragInProgress || !d.scrollbarState.barWiggleRoom || bypassVisibleBar ? 0 : 0.4;
});
// cancel transition: possible pending (also, delayed) transition
scrollbarGlyph
.transition().delay(0).duration(0);
scrollbarGlyph
.transition().delay(c.scrollbarHideDelay).duration(c.scrollbarHideDuration)
.attr('stroke-opacity', 0);
var scrollbarCaptureZone = scrollbar.selectAll('.' + c.cn.scrollbarCaptureZone)
.data(gup.repeat, gup.keyFun);
scrollbarCaptureZone.enter()
.append('line')
.classed(c.cn.scrollbarCaptureZone, true)
.attr('stroke', 'white')
.attr('stroke-opacity', 0.01) // some browser might get rid of a 0 opacity element
.attr('stroke-width', c.scrollbarCaptureWidth)
.attr('stroke-linecap', 'butt')
.attr('y1', 0)
.on('mousedown', function(d) {
var y = d3.event.y;
var bbox = this.getBoundingClientRect();
var s = d.scrollbarState;
var pixelVal = y - bbox.top;
var inverseScale = d3.scale.linear().domain([0, s.scrollableAreaHeight]).range([0, s.totalHeight]).clamp(true);
if(!(s.topY <= pixelVal && pixelVal <= s.bottomY)) {
makeDragRow(gd, tableControlView, null, inverseScale(pixelVal - s.barLength / 2))(d);
}
})
.call(d3.behavior.drag()
.origin(function(d) {
d3.event.stopPropagation();
d.scrollbarState.scrollbarScrollInProgress = true;
return d;
})
.on('drag', makeDragRow(gd, tableControlView))
.on('dragend', function() {
// fixme emit Plotly event
})
);
scrollbarCaptureZone
.attr('y2', function(d) {
return d.scrollbarState.scrollableAreaHeight;
});
// Remove scroll glyph and capture zone on static plots
// as they don't render properly when converted to PDF
// in the Chrome PDF viewer
// https://github.com/plotly/streambed/issues/11618
if(gd._context.staticPlot) {
scrollbarGlyph.remove();
scrollbarCaptureZone.remove();
}
}
function renderColumnCellTree(gd, tableControlView, columnBlock, allColumnBlock) {
// fixme this perf hotspot
// this is performance critical code as scrolling calls it on every revolver switch
// it appears sufficiently fast but there are plenty of low-hanging fruits for performance optimization
var columnCells = renderColumnCells(columnBlock);
var columnCell = renderColumnCell(columnCells);
supplyStylingValues(columnCell);
var cellRect = renderCellRect(columnCell);
sizeAndStyleRect(cellRect);
var cellTextHolder = renderCellTextHolder(columnCell);
var cellText = renderCellText(cellTextHolder);
setFont(cellText);
populateCellText(cellText, tableControlView, allColumnBlock, gd);
// doing this at the end when text, and text stlying are set
setCellHeightAndPositionY(columnCell);
}
function renderColumnCells(columnBlock) {
var columnCells = columnBlock.selectAll('.' + c.cn.columnCells)
.data(gup.repeat, gup.keyFun);
columnCells.enter()
.append('g')
.classed(c.cn.columnCells, true);
columnCells.exit()
.remove();
return columnCells;
}
function renderColumnCell(columnCells) {
var columnCell = columnCells.selectAll('.' + c.cn.columnCell)
.data(splitData.splitToCells, function(d) {return d.keyWithinBlock;});
columnCell.enter()
.append('g')
.classed(c.cn.columnCell, true);
columnCell.exit()
.remove();
return columnCell;
}
function renderCellRect(columnCell) {
var cellRect = columnCell.selectAll('.' + c.cn.cellRect)
.data(gup.repeat, function(d) {return d.keyWithinBlock;});
cellRect.enter()
.append('rect')
.classed(c.cn.cellRect, true);
return cellRect;
}
function renderCellText(cellTextHolder) {
var cellText = cellTextHolder.selectAll('.' + c.cn.cellText)
.data(gup.repeat, function(d) {return d.keyWithinBlock;});
cellText.enter()
.append('text')
.classed(c.cn.cellText, true)
.style('cursor', function() {return 'auto';})
.on('mousedown', function() {d3.event.stopPropagation();});
return cellText;
}
function renderCellTextHolder(columnCell) {
var cellTextHolder = columnCell.selectAll('.' + c.cn.cellTextHolder)
.data(gup.repeat, function(d) {return d.keyWithinBlock;});
cellTextHolder.enter()
.append('g')
.classed(c.cn.cellTextHolder, true)
.style('shape-rendering', 'geometricPrecision');
return cellTextHolder;
}
function supplyStylingValues(columnCell) {
columnCell
.each(function(d, i) {
var spec = d.calcdata.cells.font;
var col = d.column.specIndex;
var font = {
size: gridPick(spec.size, col, i),
color: gridPick(spec.color, col, i),
family: gridPick(spec.family, col, i)
};
d.rowNumber = d.key;
d.align = gridPick(d.calcdata.cells.align, col, i);
d.cellBorderWidth = gridPick(d.calcdata.cells.line.width, col, i);
d.font = font;
});
}
function setFont(cellText) {
cellText
.each(function(d) {
Drawing.font(d3.select(this), d.font);
});
}
function sizeAndStyleRect(cellRect) {
cellRect
.attr('width', function(d) {return d.column.columnWidth;})
.attr('stroke-width', function(d) {return d.cellBorderWidth;})
.each(function(d) {
var atomicSelection = d3.select(this);
Color.stroke(atomicSelection, gridPick(d.calcdata.cells.line.color, d.column.specIndex, d.rowNumber));
Color.fill(atomicSelection, gridPick(d.calcdata.cells.fill.color, d.column.specIndex, d.rowNumber));
});
}
function populateCellText(cellText, tableControlView, allColumnBlock, gd) {
cellText
.text(function(d) {
var col = d.column.specIndex;
var row = d.rowNumber;
var userSuppliedContent = d.value;
var stringSupplied = (typeof userSuppliedContent === 'string');
var hasBreaks = stringSupplied && userSuppliedContent.match(/
/i);
var userBrokenText = !stringSupplied || hasBreaks;
d.mayHaveMarkup = stringSupplied && userSuppliedContent.match(/[<&>]/);
var latex = isLatex(userSuppliedContent);
d.latex = latex;
var prefix = latex ? '' : gridPick(d.calcdata.cells.prefix, col, row) || '';
var suffix = latex ? '' : gridPick(d.calcdata.cells.suffix, col, row) || '';
var format = latex ? null : gridPick(d.calcdata.cells.format, col, row) || null;
var prefixSuffixedText = prefix + (format ? numberFormat(format)(d.value) : d.value) + suffix;
var hasWrapSplitCharacter;
d.wrappingNeeded = !d.wrapped && !userBrokenText && !latex && (hasWrapSplitCharacter = hasWrapCharacter(prefixSuffixedText));
d.cellHeightMayIncrease = hasBreaks || latex || d.mayHaveMarkup || (hasWrapSplitCharacter === void(0) ? hasWrapCharacter(prefixSuffixedText) : hasWrapSplitCharacter);
d.needsConvertToTspans = d.mayHaveMarkup || d.wrappingNeeded || d.latex;
var textToRender;
if(d.wrappingNeeded) {
var hrefPreservedText = c.wrapSplitCharacter === ' ' ? prefixSuffixedText.replace(/ pTop) {
pages.push(blockIndex);
}
pTop += rowsHeight;
// consider this nice final optimization; put it in `for` condition - caveat, currently the
// block.allRowsHeight relies on being invalidated, so enabling this opt may not be safe
// if(pages.length > 1) break;
}
return pages;
}
function updateBlockYPosition(gd, cellsColumnBlock, tableControlView) {
var d = flatData(cellsColumnBlock)[0];
if(d === undefined) return;
var blocks = d.rowBlocks;
var calcdata = d.calcdata;
var bottom = firstRowAnchor(blocks, blocks.length);
var scrollHeight = d.calcdata.groupHeight - headerHeight(d);
var scrollY = calcdata.scrollY = Math.max(0, Math.min(bottom - scrollHeight, calcdata.scrollY));
var pages = findPagesAndCacheHeights(blocks, scrollY, scrollHeight);
if(pages.length === 1) {
if(pages[0] === blocks.length - 1) {
pages.unshift(pages[0] - 1);
} else {
pages.push(pages[0] + 1);
}
}
// make phased out page jump by 2 while leaving stationary page intact
if(pages[0] % 2) {
pages.reverse();
}
cellsColumnBlock
.each(function(d, i) {
// these values will also be needed when a block is translated again due to growing cell height
d.page = pages[i];
d.scrollY = scrollY;
});
cellsColumnBlock
.attr('transform', function(d) {
var yTranslate = firstRowAnchor(d.rowBlocks, d.page) - d.scrollY;
return strTranslate(0, yTranslate);
});
// conditionally rerendering panel 0 and 1
if(gd) {
conditionalPanelRerender(gd, tableControlView, cellsColumnBlock, pages, d.prevPages, d, 0);
conditionalPanelRerender(gd, tableControlView, cellsColumnBlock, pages, d.prevPages, d, 1);
renderScrollbarKit(tableControlView, gd);
}
}
function makeDragRow(gd, allTableControlView, optionalMultiplier, optionalPosition) {
return function dragRow(eventD) {
// may come from whichever DOM event target: drag, wheel, bar... eventD corresponds to event target
var d = eventD.calcdata ? eventD.calcdata : eventD;
var tableControlView = allTableControlView.filter(function(dd) {return d.key === dd.key;});
var multiplier = optionalMultiplier || d.scrollbarState.dragMultiplier;
var initialScrollY = d.scrollY;
d.scrollY = optionalPosition === void(0) ? d.scrollY + multiplier * d3.event.dy : optionalPosition;
var cellsColumnBlock = tableControlView.selectAll('.' + c.cn.yColumn).selectAll('.' + c.cn.columnBlock).filter(cellsBlock);
updateBlockYPosition(gd, cellsColumnBlock, tableControlView);
// return false if we've "used" the scroll, ie it did something,
// so the event shouldn't bubble (if appropriate)
return d.scrollY === initialScrollY;
};
}
function conditionalPanelRerender(gd, tableControlView, cellsColumnBlock, pages, prevPages, d, revolverIndex) {
var shouldComponentUpdate = pages[revolverIndex] !== prevPages[revolverIndex];
if(shouldComponentUpdate) {
clearTimeout(d.currentRepaint[revolverIndex]);
d.currentRepaint[revolverIndex] = setTimeout(function() {
// setTimeout might lag rendering but yields a smoother scroll, because fast scrolling makes
// some repaints invisible ie. wasteful (DOM work blocks the main thread)
var toRerender = cellsColumnBlock.filter(function(d, i) {return i === revolverIndex && pages[i] !== prevPages[i];});
renderColumnCellTree(gd, tableControlView, toRerender, cellsColumnBlock);
prevPages[revolverIndex] = pages[revolverIndex];
});
}
}
function wrapTextMaker(columnBlock, element, tableControlView, gd) {
return function wrapText() {
var cellTextHolder = d3.select(element.parentNode);
cellTextHolder
.each(function(d) {
var fragments = d.fragments;
cellTextHolder.selectAll('tspan.line').each(function(dd, i) {
fragments[i].width = this.getComputedTextLength();
});
// last element is only for measuring the separator character, so it's ignored:
var separatorLength = fragments[fragments.length - 1].width;
var rest = fragments.slice(0, -1);
var currentRow = [];
var currentAddition, currentAdditionLength;
var currentRowLength = 0;
var rowLengthLimit = d.column.columnWidth - 2 * c.cellPad;
d.value = '';
while(rest.length) {
currentAddition = rest.shift();
currentAdditionLength = currentAddition.width + separatorLength;
if(currentRowLength + currentAdditionLength > rowLengthLimit) {
d.value += currentRow.join(c.wrapSpacer) + c.lineBreaker;
currentRow = [];
currentRowLength = 0;
}
currentRow.push(currentAddition.text);
currentRowLength += currentAdditionLength;
}
if(currentRowLength) {
d.value += currentRow.join(c.wrapSpacer);
}
d.wrapped = true;
});
// the pre-wrapped text was rendered only for the text measurements
cellTextHolder.selectAll('tspan.line').remove();
// resupply text, now wrapped
populateCellText(cellTextHolder.select('.' + c.cn.cellText), tableControlView, columnBlock, gd);
d3.select(element.parentNode.parentNode).call(setCellHeightAndPositionY);
};
}
function updateYPositionMaker(columnBlock, element, tableControlView, gd, d) {
return function updateYPosition() {
if(d.settledY) return;
var cellTextHolder = d3.select(element.parentNode);
var l = getBlock(d);
var rowIndex = d.key - l.firstRowIndex;
var declaredRowHeight = l.rows[rowIndex].rowHeight;
var requiredHeight = d.cellHeightMayIncrease ? element.parentNode.getBoundingClientRect().height + 2 * c.cellPad : declaredRowHeight;
var finalHeight = Math.max(requiredHeight, declaredRowHeight);
var increase = finalHeight - l.rows[rowIndex].rowHeight;
if(increase) {
// current row height increased
l.rows[rowIndex].rowHeight = finalHeight;
columnBlock
.selectAll('.' + c.cn.columnCell)
.call(setCellHeightAndPositionY);
updateBlockYPosition(null, columnBlock.filter(cellsBlock), 0);
// if d.column.type === 'header', then the scrollbar has to be pushed downward to the scrollable area
// if d.column.type === 'cells', it can still be relevant if total scrolling content height is less than the
// scrollable window, as increases to row heights may need scrollbar updates
renderScrollbarKit(tableControlView, gd, true);
}
cellTextHolder
.attr('transform', function() {
// this code block is only invoked for items where d.cellHeightMayIncrease is truthy
var element = this;
var columnCellElement = element.parentNode;
var box = columnCellElement.getBoundingClientRect();
var rectBox = d3.select(element.parentNode).select('.' + c.cn.cellRect).node().getBoundingClientRect();
var currentTransform = element.transform.baseVal.consolidate();
var yPosition = rectBox.top - box.top + (currentTransform ? currentTransform.matrix.f : c.cellPad);
return strTranslate(xPosition(d, d3.select(element.parentNode).select('.' + c.cn.cellTextHolder).node().getBoundingClientRect().width), yPosition);
});
d.settledY = true;
};
}
function xPosition(d, optionalWidth) {
switch(d.align) {
case 'left': return c.cellPad;
case 'right': return d.column.columnWidth - (optionalWidth || 0) - c.cellPad;
case 'center': return (d.column.columnWidth - (optionalWidth || 0)) / 2;
default: return c.cellPad;
}
}
function setCellHeightAndPositionY(columnCell) {
columnCell
.attr('transform', function(d) {
var headerHeight = d.rowBlocks[0].auxiliaryBlocks.reduce(function(p, n) {return p + rowsHeight(n, Infinity);}, 0);
var l = getBlock(d);
var rowAnchor = rowsHeight(l, d.key);
var yOffset = rowAnchor + headerHeight;
return strTranslate(0, yOffset);
})
.selectAll('.' + c.cn.cellRect)
.attr('height', function(d) {return getRow(getBlock(d), d.key).rowHeight;});
}
function firstRowAnchor(blocks, page) {
var total = 0;
for(var i = page - 1; i >= 0; i--) {
total += allRowsHeight(blocks[i]);
}
return total;
}
function rowsHeight(rowBlock, key) {
var total = 0;
for(var i = 0; i < rowBlock.rows.length && rowBlock.rows[i].rowIndex < key; i++) {
total += rowBlock.rows[i].rowHeight;
}
return total;
}
function allRowsHeight(rowBlock) {
var cached = rowBlock.allRowsHeight;
if(cached !== void(0)) {
return cached;
}
var total = 0;
for(var i = 0; i < rowBlock.rows.length; i++) {
total += rowBlock.rows[i].rowHeight;
}
rowBlock.allRowsHeight = total;
return total;
}
function getBlock(d) {return d.rowBlocks[d.page];}
function getRow(l, i) {return l.rows[i - l.firstRowIndex];}
},{"../../components/color":366,"../../components/drawing":388,"../../lib":503,"../../lib/gup":500,"../../lib/svg_text_utils":529,"./constants":1064,"./data_preparation_helper":1065,"./data_split_helpers":1066,"@plotly/d3":58}],1070:[function(_dereq_,module,exports){
'use strict';
var hovertemplateAttrs = _dereq_('../../plots/template_attributes').hovertemplateAttrs;
var texttemplateAttrs = _dereq_('../../plots/template_attributes').texttemplateAttrs;
var colorScaleAttrs = _dereq_('../../components/colorscale/attributes');
var domainAttrs = _dereq_('../../plots/domain').attributes;
var pieAttrs = _dereq_('../pie/attributes');
var sunburstAttrs = _dereq_('../sunburst/attributes');
var constants = _dereq_('./constants');
var extendFlat = _dereq_('../../lib/extend').extendFlat;
module.exports = {
labels: sunburstAttrs.labels,
parents: sunburstAttrs.parents,
values: sunburstAttrs.values,
branchvalues: sunburstAttrs.branchvalues,
count: sunburstAttrs.count,
level: sunburstAttrs.level,
maxdepth: sunburstAttrs.maxdepth,
tiling: {
packing: {
valType: 'enumerated',
values: [
'squarify',
'binary',
'dice',
'slice',
'slice-dice',
'dice-slice'
],
dflt: 'squarify',
editType: 'plot',
},
squarifyratio: {
valType: 'number',
min: 1,
dflt: 1,
editType: 'plot',
},
flip: {
valType: 'flaglist',
flags: [
'x',
'y'
],
dflt: '',
editType: 'plot',
},
pad: {
valType: 'number',
min: 0,
dflt: 3,
editType: 'plot',
},
editType: 'calc',
},
marker: extendFlat({
pad: {
t: {
valType: 'number',
min: 0,
editType: 'plot',
},
l: {
valType: 'number',
min: 0,
editType: 'plot',
},
r: {
valType: 'number',
min: 0,
editType: 'plot',
},
b: {
valType: 'number',
min: 0,
editType: 'plot',
},
editType: 'calc'
},
colors: sunburstAttrs.marker.colors,
depthfade: {
valType: 'enumerated',
values: [true, false, 'reversed'],
editType: 'style',
},
line: sunburstAttrs.marker.line,
editType: 'calc'
},
colorScaleAttrs('marker', {
colorAttr: 'colors',
anim: false // TODO: set to anim: true?
})
),
pathbar: {
visible: {
valType: 'boolean',
dflt: true,
editType: 'plot',
},
side: {
valType: 'enumerated',
values: [
'top',
'bottom'
],
dflt: 'top',
editType: 'plot',
},
edgeshape: {
valType: 'enumerated',
values: [
'>',
'<',
'|',
'/',
'\\'
],
dflt: '>',
editType: 'plot',
},
thickness: {
valType: 'number',
min: 12,
editType: 'plot',
},
textfont: extendFlat({}, pieAttrs.textfont, {
}),
editType: 'calc'
},
text: pieAttrs.text,
textinfo: sunburstAttrs.textinfo,
// TODO: incorporate `label` and `value` in the eventData
texttemplate: texttemplateAttrs({editType: 'plot'}, {
keys: constants.eventDataKeys.concat(['label', 'value'])
}),
hovertext: pieAttrs.hovertext,
hoverinfo: sunburstAttrs.hoverinfo,
hovertemplate: hovertemplateAttrs({}, {
keys: constants.eventDataKeys
}),
textfont: pieAttrs.textfont,
insidetextfont: pieAttrs.insidetextfont,
outsidetextfont: extendFlat({}, pieAttrs.outsidetextfont, {
}),
textposition: {
valType: 'enumerated',
values: [
'top left', 'top center', 'top right',
'middle left', 'middle center', 'middle right',
'bottom left', 'bottom center', 'bottom right'
],
dflt: 'top left',
editType: 'plot',
},
sort: pieAttrs.sort,
root: sunburstAttrs.root,
domain: domainAttrs({name: 'treemap', trace: true, editType: 'calc'}),
};
},{"../../components/colorscale/attributes":373,"../../lib/extend":493,"../../plots/domain":584,"../../plots/template_attributes":633,"../pie/attributes":899,"../sunburst/attributes":1044,"./constants":1073}],1071:[function(_dereq_,module,exports){
'use strict';
var plots = _dereq_('../../plots/plots');
exports.name = 'treemap';
exports.plot = function(gd, traces, transitionOpts, makeOnCompleteCallback) {
plots.plotBasePlot(exports.name, gd, traces, transitionOpts, makeOnCompleteCallback);
};
exports.clean = function(newFullData, newFullLayout, oldFullData, oldFullLayout) {
plots.cleanBasePlot(exports.name, newFullData, newFullLayout, oldFullData, oldFullLayout);
};
},{"../../plots/plots":619}],1072:[function(_dereq_,module,exports){
'use strict';
var calc = _dereq_('../sunburst/calc');
exports.calc = function(gd, trace) {
return calc.calc(gd, trace);
};
exports.crossTraceCalc = function(gd) {
return calc._runCrossTraceCalc('treemap', gd);
};
},{"../sunburst/calc":1046}],1073:[function(_dereq_,module,exports){
'use strict';
module.exports = {
CLICK_TRANSITION_TIME: 750,
CLICK_TRANSITION_EASING: 'poly',
eventDataKeys: [
// string
'currentPath',
'root',
'entry',
// no need to add 'parent' here
// percentages i.e. ratios
'percentRoot',
'percentEntry',
'percentParent'
],
gapWithPathbar: 1 // i.e. one pixel
};
},{}],1074:[function(_dereq_,module,exports){
'use strict';
var Lib = _dereq_('../../lib');
var attributes = _dereq_('./attributes');
var Color = _dereq_('../../components/color');
var handleDomainDefaults = _dereq_('../../plots/domain').defaults;
var handleText = _dereq_('../bar/defaults').handleText;
var TEXTPAD = _dereq_('../bar/constants').TEXTPAD;
var Colorscale = _dereq_('../../components/colorscale');
var hasColorscale = Colorscale.hasColorscale;
var colorscaleDefaults = Colorscale.handleDefaults;
module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout) {
function coerce(attr, dflt) {
return Lib.coerce(traceIn, traceOut, attributes, attr, dflt);
}
var labels = coerce('labels');
var parents = coerce('parents');
if(!labels || !labels.length || !parents || !parents.length) {
traceOut.visible = false;
return;
}
var vals = coerce('values');
if(vals && vals.length) {
coerce('branchvalues');
} else {
coerce('count');
}
coerce('level');
coerce('maxdepth');
var packing = coerce('tiling.packing');
if(packing === 'squarify') {
coerce('tiling.squarifyratio');
}
coerce('tiling.flip');
coerce('tiling.pad');
var text = coerce('text');
coerce('texttemplate');
if(!traceOut.texttemplate) coerce('textinfo', Array.isArray(text) ? 'text+label' : 'label');
coerce('hovertext');
coerce('hovertemplate');
var hasPathbar = coerce('pathbar.visible');
var textposition = 'auto';
handleText(traceIn, traceOut, layout, coerce, textposition, {
hasPathbar: hasPathbar,
moduleHasSelected: false,
moduleHasUnselected: false,
moduleHasConstrain: false,
moduleHasCliponaxis: false,
moduleHasTextangle: false,
moduleHasInsideanchor: false
});
coerce('textposition');
var bottomText = traceOut.textposition.indexOf('bottom') !== -1;
var lineWidth = coerce('marker.line.width');
if(lineWidth) coerce('marker.line.color', layout.paper_bgcolor);
var colors = coerce('marker.colors');
var withColorscale = traceOut._hasColorscale = (
hasColorscale(traceIn, 'marker', 'colors') ||
(traceIn.marker || {}).coloraxis // N.B. special logic to consider "values" colorscales
);
if(withColorscale) {
colorscaleDefaults(traceIn, traceOut, layout, coerce, {prefix: 'marker.', cLetter: 'c'});
} else {
coerce('marker.depthfade', !(colors || []).length);
}
var headerSize = traceOut.textfont.size * 2;
coerce('marker.pad.t', bottomText ? headerSize / 4 : headerSize);
coerce('marker.pad.l', headerSize / 4);
coerce('marker.pad.r', headerSize / 4);
coerce('marker.pad.b', bottomText ? headerSize : headerSize / 4);
traceOut._hovered = {
marker: {
line: {
width: 2,
color: Color.contrast(layout.paper_bgcolor)
}
}
};
if(hasPathbar) {
// This works even for multi-line labels as treemap pathbar trim out line breaks
coerce('pathbar.thickness', traceOut.pathbar.textfont.size + 2 * TEXTPAD);
coerce('pathbar.side');
coerce('pathbar.edgeshape');
}
coerce('sort');
coerce('root.color');
handleDomainDefaults(traceOut, layout, coerce);
// do not support transforms for now
traceOut._length = null;
};
},{"../../components/color":366,"../../components/colorscale":378,"../../lib":503,"../../plots/domain":584,"../bar/constants":650,"../bar/defaults":652,"./attributes":1070}],1075:[function(_dereq_,module,exports){
'use strict';
var d3 = _dereq_('@plotly/d3');
var helpers = _dereq_('../sunburst/helpers');
var uniformText = _dereq_('../bar/uniform_text');
var clearMinTextSize = uniformText.clearMinTextSize;
var resizeText = _dereq_('../bar/style').resizeText;
var plotOne = _dereq_('./plot_one');
module.exports = function _plot(gd, cdmodule, transitionOpts, makeOnCompleteCallback, opts) {
var type = opts.type;
var drawDescendants = opts.drawDescendants;
var fullLayout = gd._fullLayout;
var layer = fullLayout['_' + type + 'layer'];
var join, onComplete;
// If transition config is provided, then it is only a partial replot and traces not
// updated are removed.
var isFullReplot = !transitionOpts;
clearMinTextSize(type, fullLayout);
join = layer.selectAll('g.trace.' + type)
.data(cdmodule, function(cd) { return cd[0].trace.uid; });
join.enter().append('g')
.classed('trace', true)
.classed(type, true);
join.order();
if(!fullLayout.uniformtext.mode && helpers.hasTransition(transitionOpts)) {
if(makeOnCompleteCallback) {
// If it was passed a callback to register completion, make a callback. If
// this is created, then it must be executed on completion, otherwise the
// pos-transition redraw will not execute:
onComplete = makeOnCompleteCallback();
}
var transition = d3.transition()
.duration(transitionOpts.duration)
.ease(transitionOpts.easing)
.each('end', function() { onComplete && onComplete(); })
.each('interrupt', function() { onComplete && onComplete(); });
transition.each(function() {
// Must run the selection again since otherwise enters/updates get grouped together
// and these get executed out of order. Except we need them in order!
layer.selectAll('g.trace').each(function(cd) {
plotOne(gd, cd, this, transitionOpts, drawDescendants);
});
});
} else {
join.each(function(cd) {
plotOne(gd, cd, this, transitionOpts, drawDescendants);
});
if(fullLayout.uniformtext.mode) {
resizeText(gd, layer.selectAll('.trace'), type);
}
}
if(isFullReplot) {
join.exit().remove();
}
};
},{"../bar/style":662,"../bar/uniform_text":664,"../sunburst/helpers":1050,"./plot_one":1084,"@plotly/d3":58}],1076:[function(_dereq_,module,exports){
'use strict';
var d3 = _dereq_('@plotly/d3');
var Lib = _dereq_('../../lib');
var Drawing = _dereq_('../../components/drawing');
var svgTextUtils = _dereq_('../../lib/svg_text_utils');
var partition = _dereq_('./partition');
var styleOne = _dereq_('./style').styleOne;
var constants = _dereq_('./constants');
var helpers = _dereq_('../sunburst/helpers');
var attachFxHandlers = _dereq_('../sunburst/fx');
var onPathbar = true; // for Ancestors
module.exports = function drawAncestors(gd, cd, entry, slices, opts) {
var barDifY = opts.barDifY;
var width = opts.width;
var height = opts.height;
var viewX = opts.viewX;
var viewY = opts.viewY;
var pathSlice = opts.pathSlice;
var toMoveInsideSlice = opts.toMoveInsideSlice;
var strTransform = opts.strTransform;
var hasTransition = opts.hasTransition;
var handleSlicesExit = opts.handleSlicesExit;
var makeUpdateSliceInterpolator = opts.makeUpdateSliceInterpolator;
var makeUpdateTextInterpolator = opts.makeUpdateTextInterpolator;
var refRect = {};
var fullLayout = gd._fullLayout;
var cd0 = cd[0];
var trace = cd0.trace;
var hierarchy = cd0.hierarchy;
var eachWidth = width / trace._entryDepth;
var pathIds = helpers.listPath(entry.data, 'id');
var sliceData = partition(hierarchy.copy(), [width, height], {
packing: 'dice',
pad: {
inner: 0,
top: 0,
left: 0,
right: 0,
bottom: 0
}
}).descendants();
// edit slices that show up on graph
sliceData = sliceData.filter(function(pt) {
var level = pathIds.indexOf(pt.data.id);
if(level === -1) return false;
pt.x0 = eachWidth * level;
pt.x1 = eachWidth * (level + 1);
pt.y0 = barDifY;
pt.y1 = barDifY + height;
pt.onPathbar = true;
return true;
});
sliceData.reverse();
slices = slices.data(sliceData, helpers.getPtId);
slices.enter().append('g')
.classed('pathbar', true);
handleSlicesExit(slices, onPathbar, refRect, [width, height], pathSlice);
slices.order();
var updateSlices = slices;
if(hasTransition) {
updateSlices = updateSlices.transition().each('end', function() {
// N.B. gd._transitioning is (still) *true* by the time
// transition updates get here
var sliceTop = d3.select(this);
helpers.setSliceCursor(sliceTop, gd, {
hideOnRoot: false,
hideOnLeaves: false,
isTransitioning: false
});
});
}
updateSlices.each(function(pt) {
// for bbox
pt._x0 = viewX(pt.x0);
pt._x1 = viewX(pt.x1);
pt._y0 = viewY(pt.y0);
pt._y1 = viewY(pt.y1);
pt._hoverX = viewX(pt.x1 - Math.min(width, height) / 2);
pt._hoverY = viewY(pt.y1 - height / 2);
var sliceTop = d3.select(this);
var slicePath = Lib.ensureSingle(sliceTop, 'path', 'surface', function(s) {
s.style('pointer-events', 'all');
});
if(hasTransition) {
slicePath.transition().attrTween('d', function(pt2) {
var interp = makeUpdateSliceInterpolator(pt2, onPathbar, refRect, [width, height]);
return function(t) { return pathSlice(interp(t)); };
});
} else {
slicePath.attr('d', pathSlice);
}
sliceTop
.call(attachFxHandlers, entry, gd, cd, {
styleOne: styleOne,
eventDataKeys: constants.eventDataKeys,
transitionTime: constants.CLICK_TRANSITION_TIME,
transitionEasing: constants.CLICK_TRANSITION_EASING
})
.call(helpers.setSliceCursor, gd, {
hideOnRoot: false,
hideOnLeaves: false,
isTransitioning: gd._transitioning
});
slicePath.call(styleOne, pt, trace, {
hovered: false
});
pt._text = (helpers.getPtLabel(pt) || '').split('
').join(' ') || '';
var sliceTextGroup = Lib.ensureSingle(sliceTop, 'g', 'slicetext');
var sliceText = Lib.ensureSingle(sliceTextGroup, 'text', '', function(s) {
// prohibit tex interpretation until we can handle
// tex and regular text together
s.attr('data-notex', 1);
});
var font = Lib.ensureUniformFontSize(gd, helpers.determineTextFont(trace, pt, fullLayout.font, {
onPathbar: true
}));
sliceText.text(pt._text || ' ') // use one space character instead of a blank string to avoid jumps during transition
.classed('slicetext', true)
.attr('text-anchor', 'start')
.call(Drawing.font, font)
.call(svgTextUtils.convertToTspans, gd);
pt.textBB = Drawing.bBox(sliceText.node());
pt.transform = toMoveInsideSlice(pt, {
fontSize: font.size,
onPathbar: true
});
pt.transform.fontSize = font.size;
if(hasTransition) {
sliceText.transition().attrTween('transform', function(pt2) {
var interp = makeUpdateTextInterpolator(pt2, onPathbar, refRect, [width, height]);
return function(t) { return strTransform(interp(t)); };
});
} else {
sliceText.attr('transform', strTransform(pt));
}
});
};
},{"../../components/drawing":388,"../../lib":503,"../../lib/svg_text_utils":529,"../sunburst/fx":1049,"../sunburst/helpers":1050,"./constants":1073,"./partition":1082,"./style":1085,"@plotly/d3":58}],1077:[function(_dereq_,module,exports){
'use strict';
var d3 = _dereq_('@plotly/d3');
var Lib = _dereq_('../../lib');
var Drawing = _dereq_('../../components/drawing');
var svgTextUtils = _dereq_('../../lib/svg_text_utils');
var partition = _dereq_('./partition');
var styleOne = _dereq_('./style').styleOne;
var constants = _dereq_('./constants');
var helpers = _dereq_('../sunburst/helpers');
var attachFxHandlers = _dereq_('../sunburst/fx');
var formatSliceLabel = _dereq_('../sunburst/plot').formatSliceLabel;
var onPathbar = false; // for Descendants
module.exports = function drawDescendants(gd, cd, entry, slices, opts) {
var width = opts.width;
var height = opts.height;
var viewX = opts.viewX;
var viewY = opts.viewY;
var pathSlice = opts.pathSlice;
var toMoveInsideSlice = opts.toMoveInsideSlice;
var strTransform = opts.strTransform;
var hasTransition = opts.hasTransition;
var handleSlicesExit = opts.handleSlicesExit;
var makeUpdateSliceInterpolator = opts.makeUpdateSliceInterpolator;
var makeUpdateTextInterpolator = opts.makeUpdateTextInterpolator;
var prevEntry = opts.prevEntry;
var refRect = {};
var fullLayout = gd._fullLayout;
var cd0 = cd[0];
var trace = cd0.trace;
var hasLeft = trace.textposition.indexOf('left') !== -1;
var hasRight = trace.textposition.indexOf('right') !== -1;
var hasBottom = trace.textposition.indexOf('bottom') !== -1;
var noRoomForHeader = (!hasBottom && !trace.marker.pad.t) || (hasBottom && !trace.marker.pad.b);
// N.B. slice data isn't the calcdata,
// grab corresponding calcdata item in sliceData[i].data.data
var allData = partition(entry, [width, height], {
packing: trace.tiling.packing,
squarifyratio: trace.tiling.squarifyratio,
flipX: trace.tiling.flip.indexOf('x') > -1,
flipY: trace.tiling.flip.indexOf('y') > -1,
pad: {
inner: trace.tiling.pad,
top: trace.marker.pad.t,
left: trace.marker.pad.l,
right: trace.marker.pad.r,
bottom: trace.marker.pad.b,
}
});
var sliceData = allData.descendants();
var minVisibleDepth = Infinity;
var maxVisibleDepth = -Infinity;
sliceData.forEach(function(pt) {
var depth = pt.depth;
if(depth >= trace._maxDepth) {
// hide slices that won't show up on graph
pt.x0 = pt.x1 = (pt.x0 + pt.x1) / 2;
pt.y0 = pt.y1 = (pt.y0 + pt.y1) / 2;
} else {
minVisibleDepth = Math.min(minVisibleDepth, depth);
maxVisibleDepth = Math.max(maxVisibleDepth, depth);
}
});
slices = slices.data(sliceData, helpers.getPtId);
trace._maxVisibleLayers = isFinite(maxVisibleDepth) ? maxVisibleDepth - minVisibleDepth + 1 : 0;
slices.enter().append('g')
.classed('slice', true);
handleSlicesExit(slices, onPathbar, refRect, [width, height], pathSlice);
slices.order();
// next coords of previous entry
var nextOfPrevEntry = null;
if(hasTransition && prevEntry) {
var prevEntryId = helpers.getPtId(prevEntry);
slices.each(function(pt) {
if(nextOfPrevEntry === null && (helpers.getPtId(pt) === prevEntryId)) {
nextOfPrevEntry = {
x0: pt.x0,
x1: pt.x1,
y0: pt.y0,
y1: pt.y1
};
}
});
}
var getRefRect = function() {
return nextOfPrevEntry || {
x0: 0,
x1: width,
y0: 0,
y1: height
};
};
var updateSlices = slices;
if(hasTransition) {
updateSlices = updateSlices.transition().each('end', function() {
// N.B. gd._transitioning is (still) *true* by the time
// transition updates get here
var sliceTop = d3.select(this);
helpers.setSliceCursor(sliceTop, gd, {
hideOnRoot: true,
hideOnLeaves: false,
isTransitioning: false
});
});
}
updateSlices.each(function(pt) {
var isHeader = helpers.isHeader(pt, trace);
// for bbox
pt._x0 = viewX(pt.x0);
pt._x1 = viewX(pt.x1);
pt._y0 = viewY(pt.y0);
pt._y1 = viewY(pt.y1);
pt._hoverX = viewX(pt.x1 - trace.marker.pad.r),
pt._hoverY = hasBottom ?
viewY(pt.y1 - trace.marker.pad.b / 2) :
viewY(pt.y0 + trace.marker.pad.t / 2);
var sliceTop = d3.select(this);
var slicePath = Lib.ensureSingle(sliceTop, 'path', 'surface', function(s) {
s.style('pointer-events', 'all');
});
if(hasTransition) {
slicePath.transition().attrTween('d', function(pt2) {
var interp = makeUpdateSliceInterpolator(pt2, onPathbar, getRefRect(), [width, height]);
return function(t) { return pathSlice(interp(t)); };
});
} else {
slicePath.attr('d', pathSlice);
}
sliceTop
.call(attachFxHandlers, entry, gd, cd, {
styleOne: styleOne,
eventDataKeys: constants.eventDataKeys,
transitionTime: constants.CLICK_TRANSITION_TIME,
transitionEasing: constants.CLICK_TRANSITION_EASING
})
.call(helpers.setSliceCursor, gd, { isTransitioning: gd._transitioning });
slicePath.call(styleOne, pt, trace, {
hovered: false
});
if(pt.x0 === pt.x1 || pt.y0 === pt.y1) {
pt._text = '';
} else {
if(isHeader) {
pt._text = noRoomForHeader ? '' : helpers.getPtLabel(pt) || '';
} else {
pt._text = formatSliceLabel(pt, entry, trace, cd, fullLayout) || '';
}
}
var sliceTextGroup = Lib.ensureSingle(sliceTop, 'g', 'slicetext');
var sliceText = Lib.ensureSingle(sliceTextGroup, 'text', '', function(s) {
// prohibit tex interpretation until we can handle
// tex and regular text together
s.attr('data-notex', 1);
});
var font = Lib.ensureUniformFontSize(gd, helpers.determineTextFont(trace, pt, fullLayout.font));
sliceText.text(pt._text || ' ') // use one space character instead of a blank string to avoid jumps during transition
.classed('slicetext', true)
.attr('text-anchor', hasRight ? 'end' : (hasLeft || isHeader) ? 'start' : 'middle')
.call(Drawing.font, font)
.call(svgTextUtils.convertToTspans, gd);
pt.textBB = Drawing.bBox(sliceText.node());
pt.transform = toMoveInsideSlice(pt, {
fontSize: font.size,
isHeader: isHeader
});
pt.transform.fontSize = font.size;
if(hasTransition) {
sliceText.transition().attrTween('transform', function(pt2) {
var interp = makeUpdateTextInterpolator(pt2, onPathbar, getRefRect(), [width, height]);
return function(t) { return strTransform(interp(t)); };
});
} else {
sliceText.attr('transform', strTransform(pt));
}
});
return nextOfPrevEntry;
};
},{"../../components/drawing":388,"../../lib":503,"../../lib/svg_text_utils":529,"../sunburst/fx":1049,"../sunburst/helpers":1050,"../sunburst/plot":1054,"./constants":1073,"./partition":1082,"./style":1085,"@plotly/d3":58}],1078:[function(_dereq_,module,exports){
'use strict';
module.exports = function flipTree(node, size, opts) {
var tmp;
if(opts.swapXY) {
// swap x0 and y0
tmp = node.x0;
node.x0 = node.y0;
node.y0 = tmp;
// swap x1 and y1
tmp = node.x1;
node.x1 = node.y1;
node.y1 = tmp;
}
if(opts.flipX) {
tmp = node.x0;
node.x0 = size[0] - node.x1;
node.x1 = size[0] - tmp;
}
if(opts.flipY) {
tmp = node.y0;
node.y0 = size[1] - node.y1;
node.y1 = size[1] - tmp;
}
var children = node.children;
if(children) {
for(var i = 0; i < children.length; i++) {
flipTree(children[i], size, opts);
}
}
};
},{}],1079:[function(_dereq_,module,exports){
'use strict';
module.exports = {
moduleType: 'trace',
name: 'treemap',
basePlotModule: _dereq_('./base_plot'),
categories: [],
animatable: true,
attributes: _dereq_('./attributes'),
layoutAttributes: _dereq_('./layout_attributes'),
supplyDefaults: _dereq_('./defaults'),
supplyLayoutDefaults: _dereq_('./layout_defaults'),
calc: _dereq_('./calc').calc,
crossTraceCalc: _dereq_('./calc').crossTraceCalc,
plot: _dereq_('./plot'),
style: _dereq_('./style').style,
colorbar: _dereq_('../scatter/marker_colorbar'),
meta: {
}
};
},{"../scatter/marker_colorbar":943,"./attributes":1070,"./base_plot":1071,"./calc":1072,"./defaults":1074,"./layout_attributes":1080,"./layout_defaults":1081,"./plot":1083,"./style":1085}],1080:[function(_dereq_,module,exports){
'use strict';
module.exports = {
treemapcolorway: {
valType: 'colorlist',
editType: 'calc',
},
extendtreemapcolors: {
valType: 'boolean',
dflt: true,
editType: 'calc',
}
};
},{}],1081:[function(_dereq_,module,exports){
'use strict';
var Lib = _dereq_('../../lib');
var layoutAttributes = _dereq_('./layout_attributes');
module.exports = function supplyLayoutDefaults(layoutIn, layoutOut) {
function coerce(attr, dflt) {
return Lib.coerce(layoutIn, layoutOut, layoutAttributes, attr, dflt);
}
coerce('treemapcolorway', layoutOut.colorway);
coerce('extendtreemapcolors');
};
},{"../../lib":503,"./layout_attributes":1080}],1082:[function(_dereq_,module,exports){
'use strict';
var d3Hierarchy = _dereq_('d3-hierarchy');
var flipTree = _dereq_('./flip_tree');
module.exports = function partition(entry, size, opts) {
var flipX = opts.flipX;
var flipY = opts.flipY;
var swapXY = opts.packing === 'dice-slice';
var top = opts.pad[flipY ? 'bottom' : 'top'];
var left = opts.pad[flipX ? 'right' : 'left'];
var right = opts.pad[flipX ? 'left' : 'right'];
var bottom = opts.pad[flipY ? 'top' : 'bottom'];
var tmp;
if(swapXY) {
tmp = left;
left = top;
top = tmp;
tmp = right;
right = bottom;
bottom = tmp;
}
var result = d3Hierarchy
.treemap()
.tile(getTilingMethod(opts.packing, opts.squarifyratio))
.paddingInner(opts.pad.inner)
.paddingLeft(left)
.paddingRight(right)
.paddingTop(top)
.paddingBottom(bottom)
.size(
swapXY ? [size[1], size[0]] : size
)(entry);
if(swapXY || flipX || flipY) {
flipTree(result, size, {
swapXY: swapXY,
flipX: flipX,
flipY: flipY
});
}
return result;
};
function getTilingMethod(key, squarifyratio) {
switch(key) {
case 'squarify':
return d3Hierarchy.treemapSquarify.ratio(squarifyratio);
case 'binary':
return d3Hierarchy.treemapBinary;
case 'dice':
return d3Hierarchy.treemapDice;
case 'slice':
return d3Hierarchy.treemapSlice;
default: // i.e. 'slice-dice' | 'dice-slice'
return d3Hierarchy.treemapSliceDice;
}
}
},{"./flip_tree":1078,"d3-hierarchy":115}],1083:[function(_dereq_,module,exports){
'use strict';
var draw = _dereq_('./draw');
var drawDescendants = _dereq_('./draw_descendants');
module.exports = function _plot(gd, cdmodule, transitionOpts, makeOnCompleteCallback) {
return draw(gd, cdmodule, transitionOpts, makeOnCompleteCallback, {
type: 'treemap',
drawDescendants: drawDescendants
});
};
},{"./draw":1075,"./draw_descendants":1077}],1084:[function(_dereq_,module,exports){
'use strict';
var d3 = _dereq_('@plotly/d3');
var interpolate = _dereq_('d3-interpolate').interpolate;
var helpers = _dereq_('../sunburst/helpers');
var Lib = _dereq_('../../lib');
var TEXTPAD = _dereq_('../bar/constants').TEXTPAD;
var barPlot = _dereq_('../bar/plot');
var toMoveInsideBar = barPlot.toMoveInsideBar;
var uniformText = _dereq_('../bar/uniform_text');
var recordMinTextSize = uniformText.recordMinTextSize;
var constants = _dereq_('./constants');
var drawAncestors = _dereq_('./draw_ancestors');
function getKey(pt) {
return helpers.isHierarchyRoot(pt) ?
'' : // don't use the dummyId
helpers.getPtId(pt);
}
module.exports = function plotOne(gd, cd, element, transitionOpts, drawDescendants) {
var fullLayout = gd._fullLayout;
var cd0 = cd[0];
var trace = cd0.trace;
var type = trace.type;
var isIcicle = type === 'icicle';
var hierarchy = cd0.hierarchy;
var entry = helpers.findEntryWithLevel(hierarchy, trace.level);
var gTrace = d3.select(element);
var selAncestors = gTrace.selectAll('g.pathbar');
var selDescendants = gTrace.selectAll('g.slice');
if(!entry) {
selAncestors.remove();
selDescendants.remove();
return;
}
var isRoot = helpers.isHierarchyRoot(entry);
var hasTransition = !fullLayout.uniformtext.mode && helpers.hasTransition(transitionOpts);
var maxDepth = helpers.getMaxDepth(trace);
var hasVisibleDepth = function(pt) {
return pt.data.depth - entry.data.depth < maxDepth;
};
var gs = fullLayout._size;
var domain = trace.domain;
var vpw = gs.w * (domain.x[1] - domain.x[0]);
var vph = gs.h * (domain.y[1] - domain.y[0]);
var barW = vpw;
var barH = trace.pathbar.thickness;
var barPad = trace.marker.line.width + constants.gapWithPathbar;
var barDifY = !trace.pathbar.visible ? 0 :
trace.pathbar.side.indexOf('bottom') > -1 ? vph + barPad : -(barH + barPad);
var pathbarOrigin = {
x0: barW, // slide to the right
x1: barW,
y0: barDifY,
y1: barDifY + barH
};
var findClosestEdge = function(pt, ref, size) {
var e = trace.tiling.pad;
var isLeftOfRect = function(x) { return x - e <= ref.x0; };
var isRightOfRect = function(x) { return x + e >= ref.x1; };
var isBottomOfRect = function(y) { return y - e <= ref.y0; };
var isTopOfRect = function(y) { return y + e >= ref.y1; };
if(pt.x0 === ref.x0 && pt.x1 === ref.x1 && pt.y0 === ref.y0 && pt.y1 === ref.y1) {
return {
x0: pt.x0,
x1: pt.x1,
y0: pt.y0,
y1: pt.y1
};
}
return {
x0: isLeftOfRect(pt.x0 - e) ? 0 : isRightOfRect(pt.x0 - e) ? size[0] : pt.x0,
x1: isLeftOfRect(pt.x1 + e) ? 0 : isRightOfRect(pt.x1 + e) ? size[0] : pt.x1,
y0: isBottomOfRect(pt.y0 - e) ? 0 : isTopOfRect(pt.y0 - e) ? size[1] : pt.y0,
y1: isBottomOfRect(pt.y1 + e) ? 0 : isTopOfRect(pt.y1 + e) ? size[1] : pt.y1
};
};
// stash of 'previous' position data used by tweening functions
var prevEntry = null;
var prevLookupPathbar = {};
var prevLookupSlices = {};
var nextOfPrevEntry = null;
var getPrev = function(pt, onPathbar) {
return onPathbar ?
prevLookupPathbar[getKey(pt)] :
prevLookupSlices[getKey(pt)];
};
var getOrigin = function(pt, onPathbar, refRect, size) {
if(onPathbar) {
return prevLookupPathbar[getKey(hierarchy)] || pathbarOrigin;
} else {
var ref = prevLookupSlices[trace.level] || refRect;
if(hasVisibleDepth(pt)) { // case of an empty object - happens when maxdepth is set
return findClosestEdge(pt, ref, size);
}
}
return {};
};
// N.B. handle multiple-root special case
if(cd0.hasMultipleRoots && isRoot) {
maxDepth++;
}
trace._maxDepth = maxDepth;
trace._backgroundColor = fullLayout.paper_bgcolor;
trace._entryDepth = entry.data.depth;
trace._atRootLevel = isRoot;
var cenX = -vpw / 2 + gs.l + gs.w * (domain.x[1] + domain.x[0]) / 2;
var cenY = -vph / 2 + gs.t + gs.h * (1 - (domain.y[1] + domain.y[0]) / 2);
var viewMapX = function(x) { return cenX + x; };
var viewMapY = function(y) { return cenY + y; };
var barY0 = viewMapY(0);
var barX0 = viewMapX(0);
var viewBarX = function(x) { return barX0 + x; };
var viewBarY = function(y) { return barY0 + y; };
function pos(x, y) {
return x + ',' + y;
}
var xStart = viewBarX(0);
var limitX0 = function(p) { p.x = Math.max(xStart, p.x); };
var edgeshape = trace.pathbar.edgeshape;
// pathbar(directory) path generation fn
var pathAncestor = function(d) {
var _x0 = viewBarX(Math.max(Math.min(d.x0, d.x0), 0));
var _x1 = viewBarX(Math.min(Math.max(d.x1, d.x1), barW));
var _y0 = viewBarY(d.y0);
var _y1 = viewBarY(d.y1);
var halfH = barH / 2;
var pL = {};
var pR = {};
pL.x = _x0;
pR.x = _x1;
pL.y = pR.y = (_y0 + _y1) / 2;
var pA = {x: _x0, y: _y0};
var pB = {x: _x1, y: _y0};
var pC = {x: _x1, y: _y1};
var pD = {x: _x0, y: _y1};
if(edgeshape === '>') {
pA.x -= halfH;
pB.x -= halfH;
pC.x -= halfH;
pD.x -= halfH;
} else if(edgeshape === '/') {
pC.x -= halfH;
pD.x -= halfH;
pL.x -= halfH / 2;
pR.x -= halfH / 2;
} else if(edgeshape === '\\') {
pA.x -= halfH;
pB.x -= halfH;
pL.x -= halfH / 2;
pR.x -= halfH / 2;
} else if(edgeshape === '<') {
pL.x -= halfH;
pR.x -= halfH;
}
limitX0(pA);
limitX0(pD);
limitX0(pL);
limitX0(pB);
limitX0(pC);
limitX0(pR);
return (
'M' + pos(pA.x, pA.y) +
'L' + pos(pB.x, pB.y) +
'L' + pos(pR.x, pR.y) +
'L' + pos(pC.x, pC.y) +
'L' + pos(pD.x, pD.y) +
'L' + pos(pL.x, pL.y) +
'Z'
);
};
// slice path generation fn
var pathDescendant = function(d) {
var _x0 = viewMapX(d.x0);
var _x1 = viewMapX(d.x1);
var _y0 = viewMapY(d.y0);
var _y1 = viewMapY(d.y1);
var dx = _x1 - _x0;
var dy = _y1 - _y0;
if(!dx || !dy) return '';
var FILLET = 0; // TODO: may expose this constant
var r = (
dx > 2 * FILLET &&
dy > 2 * FILLET
) ? FILLET : 0;
var arc = function(rx, ry) { return r ? 'a' + pos(r, r) + ' 0 0 1 ' + pos(rx, ry) : ''; };
return (
'M' + pos(_x0, _y0 + r) +
arc(r, -r) +
'L' + pos(_x1 - r, _y0) +
arc(r, r) +
'L' + pos(_x1, _y1 - r) +
arc(-r, r) +
'L' + pos(_x0 + r, _y1) +
arc(-r, -r) + 'Z'
);
};
var toMoveInsideSlice = function(pt, opts) {
var x0 = pt.x0;
var x1 = pt.x1;
var y0 = pt.y0;
var y1 = pt.y1;
var textBB = pt.textBB;
var hasFlag = function(f) { return trace.textposition.indexOf(f) !== -1; };
var hasBottom = hasFlag('bottom');
var hasTop = hasFlag('top') || (opts.isHeader && !hasBottom);
var anchor =
hasTop ? 'start' :
hasBottom ? 'end' : 'middle';
var hasRight = hasFlag('right');
var hasLeft = hasFlag('left') || opts.onPathbar;
var leftToRight =
hasLeft ? -1 :
hasRight ? 1 : 0;
// Note that `pad` is just an integer for `icicle`` traces where
// `pad` is a hashmap for treemap: pad.t, pad.b, pad.l, and pad.r
var pad = trace[isIcicle ? 'tiling' : 'marker'].pad;
if(opts.isHeader) {
x0 += (isIcicle ? pad : pad.l) - TEXTPAD;
x1 -= (isIcicle ? pad : pad.r) - TEXTPAD;
if(x0 >= x1) {
var mid = (x0 + x1) / 2;
x0 = mid;
x1 = mid;
}
// limit the drawing area for headers
var limY;
if(hasBottom) {
limY = y1 - (isIcicle ? pad : pad.b);
if(y0 < limY && limY < y1) y0 = limY;
} else {
limY = y0 + (isIcicle ? pad : pad.t);
if(y0 < limY && limY < y1) y1 = limY;
}
}
// position the text relative to the slice
var transform = toMoveInsideBar(x0, x1, y0, y1, textBB, {
isHorizontal: false,
constrained: true,
angle: 0,
anchor: anchor,
leftToRight: leftToRight
});
transform.fontSize = opts.fontSize;
transform.targetX = viewMapX(transform.targetX);
transform.targetY = viewMapY(transform.targetY);
if(isNaN(transform.targetX) || isNaN(transform.targetY)) {
return {};
}
if(x0 !== x1 && y0 !== y1) {
recordMinTextSize(trace.type, transform, fullLayout);
}
return {
scale: transform.scale,
rotate: transform.rotate,
textX: transform.textX,
textY: transform.textY,
anchorX: transform.anchorX,
anchorY: transform.anchorY,
targetX: transform.targetX,
targetY: transform.targetY
};
};
var interpFromParent = function(pt, onPathbar) {
var parentPrev;
var i = 0;
var Q = pt;
while(!parentPrev && i < maxDepth) { // loop to find a parent/grandParent on the previous graph
i++;
Q = Q.parent;
if(Q) {
parentPrev = getPrev(Q, onPathbar);
} else i = maxDepth;
}
return parentPrev || {};
};
var makeExitSliceInterpolator = function(pt, onPathbar, refRect, size) {
var prev = getPrev(pt, onPathbar);
var next;
if(onPathbar) {
next = pathbarOrigin;
} else {
var entryPrev = getPrev(entry, onPathbar);
if(entryPrev) {
// 'entryPrev' is here has the previous coordinates of the entry
// node, which corresponds to the last "clicked" node when zooming in
next = findClosestEdge(pt, entryPrev, size);
} else {
// this happens when maxdepth is set, when leaves must
// be removed and the entry is new (i.e. does not have a 'prev' object)
next = {};
}
}
return interpolate(prev, next);
};
var makeUpdateSliceInterpolator = function(pt, onPathbar, refRect, size, opts) {
var prev0 = getPrev(pt, onPathbar);
var prev;
if(prev0) {
// if pt already on graph, this is easy
prev = prev0;
} else {
// for new pts:
if(onPathbar) {
prev = pathbarOrigin;
} else {
if(prevEntry) {
// if trace was visible before
if(pt.parent) {
var ref = nextOfPrevEntry || refRect;
if(ref && !onPathbar) {
prev = findClosestEdge(pt, ref, size);
} else {
// if new leaf (when maxdepth is set),
// grow it from its parent node
prev = {};
Lib.extendFlat(prev, interpFromParent(pt, onPathbar));
}
} else {
prev = Lib.extendFlat({}, pt);
if(isIcicle) {
if(opts.orientation === 'h') {
if(opts.flipX) prev.x0 = pt.x1;
else prev.x1 = 0;
} else {
if(opts.flipY) prev.y0 = pt.y1;
else prev.y1 = 0;
}
}
}
} else {
prev = {};
}
}
}
return interpolate(prev, {
x0: pt.x0,
x1: pt.x1,
y0: pt.y0,
y1: pt.y1
});
};
var makeUpdateTextInterpolator = function(pt, onPathbar, refRect, size) {
var prev0 = getPrev(pt, onPathbar);
var prev = {};
var origin = getOrigin(pt, onPathbar, refRect, size);
Lib.extendFlat(prev, {
transform: toMoveInsideSlice({
x0: origin.x0,
x1: origin.x1,
y0: origin.y0,
y1: origin.y1,
textBB: pt.textBB,
_text: pt._text
}, {
isHeader: helpers.isHeader(pt, trace)
})
});
if(prev0) {
// if pt already on graph, this is easy
prev = prev0;
} else {
// for new pts:
if(pt.parent) {
Lib.extendFlat(prev, interpFromParent(pt, onPathbar));
}
}
var transform = pt.transform;
if(pt.x0 !== pt.x1 && pt.y0 !== pt.y1) {
recordMinTextSize(trace.type, transform, fullLayout);
}
return interpolate(prev, {
transform: {
scale: transform.scale,
rotate: transform.rotate,
textX: transform.textX,
textY: transform.textY,
anchorX: transform.anchorX,
anchorY: transform.anchorY,
targetX: transform.targetX,
targetY: transform.targetY
}
});
};
var handleSlicesExit = function(slices, onPathbar, refRect, size, pathSlice) {
var width = size[0];
var height = size[1];
if(hasTransition) {
slices.exit().transition()
.each(function() {
var sliceTop = d3.select(this);
var slicePath = sliceTop.select('path.surface');
slicePath.transition().attrTween('d', function(pt2) {
var interp = makeExitSliceInterpolator(pt2, onPathbar, refRect, [width, height]);
return function(t) { return pathSlice(interp(t)); };
});
var sliceTextGroup = sliceTop.select('g.slicetext');
sliceTextGroup.attr('opacity', 0);
})
.remove();
} else {
slices.exit().remove();
}
};
var strTransform = function(d) {
var transform = d.transform;
if(d.x0 !== d.x1 && d.y0 !== d.y1) {
recordMinTextSize(trace.type, transform, fullLayout);
}
return Lib.getTextTransform({
textX: transform.textX,
textY: transform.textY,
anchorX: transform.anchorX,
anchorY: transform.anchorY,
targetX: transform.targetX,
targetY: transform.targetY,
scale: transform.scale,
rotate: transform.rotate
});
};
if(hasTransition) {
// Important: do this before binding new sliceData!
selAncestors.each(function(pt) {
prevLookupPathbar[getKey(pt)] = {
x0: pt.x0,
x1: pt.x1,
y0: pt.y0,
y1: pt.y1
};
if(pt.transform) {
prevLookupPathbar[getKey(pt)].transform = {
textX: pt.transform.textX,
textY: pt.transform.textY,
anchorX: pt.transform.anchorX,
anchorY: pt.transform.anchorY,
targetX: pt.transform.targetX,
targetY: pt.transform.targetY,
scale: pt.transform.scale,
rotate: pt.transform.rotate
};
}
});
selDescendants.each(function(pt) {
prevLookupSlices[getKey(pt)] = {
x0: pt.x0,
x1: pt.x1,
y0: pt.y0,
y1: pt.y1
};
if(pt.transform) {
prevLookupSlices[getKey(pt)].transform = {
textX: pt.transform.textX,
textY: pt.transform.textY,
anchorX: pt.transform.anchorX,
anchorY: pt.transform.anchorY,
targetX: pt.transform.targetX,
targetY: pt.transform.targetY,
scale: pt.transform.scale,
rotate: pt.transform.rotate
};
}
if(!prevEntry && helpers.isEntry(pt)) {
prevEntry = pt;
}
});
}
nextOfPrevEntry = drawDescendants(gd, cd, entry, selDescendants, {
width: vpw,
height: vph,
viewX: viewMapX,
viewY: viewMapY,
pathSlice: pathDescendant,
toMoveInsideSlice: toMoveInsideSlice,
prevEntry: prevEntry,
makeUpdateSliceInterpolator: makeUpdateSliceInterpolator,
makeUpdateTextInterpolator: makeUpdateTextInterpolator,
handleSlicesExit: handleSlicesExit,
hasTransition: hasTransition,
strTransform: strTransform
});
if(trace.pathbar.visible) {
drawAncestors(gd, cd, entry, selAncestors, {
barDifY: barDifY,
width: barW,
height: barH,
viewX: viewBarX,
viewY: viewBarY,
pathSlice: pathAncestor,
toMoveInsideSlice: toMoveInsideSlice,
makeUpdateSliceInterpolator: makeUpdateSliceInterpolator,
makeUpdateTextInterpolator: makeUpdateTextInterpolator,
handleSlicesExit: handleSlicesExit,
hasTransition: hasTransition,
strTransform: strTransform
});
} else {
selAncestors.remove();
}
};
},{"../../lib":503,"../bar/constants":650,"../bar/plot":659,"../bar/uniform_text":664,"../sunburst/helpers":1050,"./constants":1073,"./draw_ancestors":1076,"@plotly/d3":58,"d3-interpolate":116}],1085:[function(_dereq_,module,exports){
'use strict';
var d3 = _dereq_('@plotly/d3');
var Color = _dereq_('../../components/color');
var Lib = _dereq_('../../lib');
var helpers = _dereq_('../sunburst/helpers');
var resizeText = _dereq_('../bar/uniform_text').resizeText;
function style(gd) {
var s = gd._fullLayout._treemaplayer.selectAll('.trace');
resizeText(gd, s, 'treemap');
s.each(function(cd) {
var gTrace = d3.select(this);
var cd0 = cd[0];
var trace = cd0.trace;
gTrace.style('opacity', trace.opacity);
gTrace.selectAll('path.surface').each(function(pt) {
d3.select(this).call(styleOne, pt, trace, {
hovered: false
});
});
});
}
function styleOne(s, pt, trace, opts) {
var hovered = (opts || {}).hovered;
var cdi = pt.data.data;
var ptNumber = cdi.i;
var lineColor;
var lineWidth;
var fillColor = cdi.color;
var isRoot = helpers.isHierarchyRoot(pt);
var opacity = 1;
if(hovered) {
lineColor = trace._hovered.marker.line.color;
lineWidth = trace._hovered.marker.line.width;
} else {
if(isRoot && fillColor === trace.root.color) {
opacity = 100;
lineColor = 'rgba(0,0,0,0)';
lineWidth = 0;
} else {
lineColor = Lib.castOption(trace, ptNumber, 'marker.line.color') || Color.defaultLine;
lineWidth = Lib.castOption(trace, ptNumber, 'marker.line.width') || 0;
if(!trace._hasColorscale && !pt.onPathbar) {
var depthfade = trace.marker.depthfade;
if(depthfade) {
var fadedColor = Color.combine(Color.addOpacity(trace._backgroundColor, 0.75), fillColor);
var n;
if(depthfade === true) {
var maxDepth = helpers.getMaxDepth(trace);
if(isFinite(maxDepth)) {
if(helpers.isLeaf(pt)) {
n = 0;
} else {
n = (trace._maxVisibleLayers) - (pt.data.depth - trace._entryDepth);
}
} else {
n = pt.data.height + 1;
}
} else { // i.e. case of depthfade === 'reversed'
n = pt.data.depth - trace._entryDepth;
if(!trace._atRootLevel) n++;
}
if(n > 0) {
for(var i = 0; i < n; i++) {
var ratio = 0.5 * i / n;
fillColor = Color.combine(Color.addOpacity(fadedColor, ratio), fillColor);
}
}
}
}
}
}
s.style('stroke-width', lineWidth)
.call(Color.fill, fillColor)
.call(Color.stroke, lineColor)
.style('opacity', opacity);
}
module.exports = {
style: style,
styleOne: styleOne
};
},{"../../components/color":366,"../../lib":503,"../bar/uniform_text":664,"../sunburst/helpers":1050,"@plotly/d3":58}],1086:[function(_dereq_,module,exports){
'use strict';
var boxAttrs = _dereq_('../box/attributes');
var extendFlat = _dereq_('../../lib/extend').extendFlat;
var axisHoverFormat = _dereq_('../../plots/cartesian/axis_format_attributes').axisHoverFormat;
module.exports = {
y: boxAttrs.y,
x: boxAttrs.x,
x0: boxAttrs.x0,
y0: boxAttrs.y0,
xhoverformat: axisHoverFormat('x'),
yhoverformat: axisHoverFormat('y'),
name: extendFlat({}, boxAttrs.name, {
}),
orientation: extendFlat({}, boxAttrs.orientation, {
}),
bandwidth: {
valType: 'number',
min: 0,
editType: 'calc',
},
scalegroup: {
valType: 'string',
dflt: '',
editType: 'calc',
},
scalemode: {
valType: 'enumerated',
values: ['width', 'count'],
dflt: 'width',
editType: 'calc',
},
spanmode: {
valType: 'enumerated',
values: ['soft', 'hard', 'manual'],
dflt: 'soft',
editType: 'calc',
},
span: {
valType: 'info_array',
items: [
{valType: 'any', editType: 'calc'},
{valType: 'any', editType: 'calc'}
],
editType: 'calc',
},
line: {
color: {
valType: 'color',
editType: 'style',
},
width: {
valType: 'number',
min: 0,
dflt: 2,
editType: 'style',
},
editType: 'plot'
},
fillcolor: boxAttrs.fillcolor,
points: extendFlat({}, boxAttrs.boxpoints, {
}),
jitter: extendFlat({}, boxAttrs.jitter, {
}),
pointpos: extendFlat({}, boxAttrs.pointpos, {
}),
width: extendFlat({}, boxAttrs.width, {
}),
marker: boxAttrs.marker,
text: boxAttrs.text,
hovertext: boxAttrs.hovertext,
hovertemplate: boxAttrs.hovertemplate,
box: {
visible: {
valType: 'boolean',
dflt: false,
editType: 'plot',
},
width: {
valType: 'number',
min: 0,
max: 1,
dflt: 0.25,
editType: 'plot',
},
fillcolor: {
valType: 'color',
editType: 'style',
},
line: {
color: {
valType: 'color',
editType: 'style',
},
width: {
valType: 'number',
min: 0,
editType: 'style',
},
editType: 'style'
},
editType: 'plot'
},
meanline: {
visible: {
valType: 'boolean',
dflt: false,
editType: 'plot',
},
color: {
valType: 'color',
editType: 'style',
},
width: {
valType: 'number',
min: 0,
editType: 'style',
},
editType: 'plot'
},
side: {
valType: 'enumerated',
values: ['both', 'positive', 'negative'],
dflt: 'both',
editType: 'calc',
},
offsetgroup: boxAttrs.offsetgroup,
alignmentgroup: boxAttrs.alignmentgroup,
selected: boxAttrs.selected,
unselected: boxAttrs.unselected,
hoveron: {
valType: 'flaglist',
flags: ['violins', 'points', 'kde'],
dflt: 'violins+points+kde',
extras: ['all'],
editType: 'style',
}
};
},{"../../lib/extend":493,"../../plots/cartesian/axis_format_attributes":557,"../box/attributes":673}],1087:[function(_dereq_,module,exports){
'use strict';
var Lib = _dereq_('../../lib');
var Axes = _dereq_('../../plots/cartesian/axes');
var boxCalc = _dereq_('../box/calc');
var helpers = _dereq_('./helpers');
var BADNUM = _dereq_('../../constants/numerical').BADNUM;
module.exports = function calc(gd, trace) {
var cd = boxCalc(gd, trace);
if(cd[0].t.empty) return cd;
var fullLayout = gd._fullLayout;
var valAxis = Axes.getFromId(
gd,
trace[trace.orientation === 'h' ? 'xaxis' : 'yaxis']
);
var spanMin = Infinity;
var spanMax = -Infinity;
var maxKDE = 0;
var maxCount = 0;
for(var i = 0; i < cd.length; i++) {
var cdi = cd[i];
var vals = cdi.pts.map(helpers.extractVal);
var bandwidth = cdi.bandwidth = calcBandwidth(trace, cdi, vals);
var span = cdi.span = calcSpan(trace, cdi, valAxis, bandwidth);
if(cdi.min === cdi.max && bandwidth === 0) {
// if span is zero and bandwidth is zero, we want a violin with zero width
span = cdi.span = [cdi.min, cdi.max];
cdi.density = [{v: 1, t: span[0]}];
cdi.bandwidth = bandwidth;
maxKDE = Math.max(maxKDE, 1);
} else {
// step that well covers the bandwidth and is multiple of span distance
var dist = span[1] - span[0];
var n = Math.ceil(dist / (bandwidth / 3));
var step = dist / n;
if(!isFinite(step) || !isFinite(n)) {
Lib.error('Something went wrong with computing the violin span');
cd[0].t.empty = true;
return cd;
}
var kde = helpers.makeKDE(cdi, trace, vals);
cdi.density = new Array(n);
for(var k = 0, t = span[0]; t < (span[1] + step / 2); k++, t += step) {
var v = kde(t);
cdi.density[k] = {v: v, t: t};
maxKDE = Math.max(maxKDE, v);
}
}
maxCount = Math.max(maxCount, vals.length);
spanMin = Math.min(spanMin, span[0]);
spanMax = Math.max(spanMax, span[1]);
}
var extremes = Axes.findExtremes(valAxis, [spanMin, spanMax], {padded: true});
trace._extremes[valAxis._id] = extremes;
if(trace.width) {
cd[0].t.maxKDE = maxKDE;
} else {
var violinScaleGroupStats = fullLayout._violinScaleGroupStats;
var scaleGroup = trace.scalegroup;
var groupStats = violinScaleGroupStats[scaleGroup];
if(groupStats) {
groupStats.maxKDE = Math.max(groupStats.maxKDE, maxKDE);
groupStats.maxCount = Math.max(groupStats.maxCount, maxCount);
} else {
violinScaleGroupStats[scaleGroup] = {
maxKDE: maxKDE,
maxCount: maxCount
};
}
}
cd[0].t.labels.kde = Lib._(gd, 'kde:');
return cd;
};
// Default to Silveman's rule of thumb
// - https://stats.stackexchange.com/a/6671
// - https://en.wikipedia.org/wiki/Kernel_density_estimation#A_rule-of-thumb_bandwidth_estimator
// - https://github.com/statsmodels/statsmodels/blob/master/statsmodels/nonparametric/bandwidths.py
function silvermanRule(len, ssd, iqr) {
var a = Math.min(ssd, iqr / 1.349);
return 1.059 * a * Math.pow(len, -0.2);
}
function calcBandwidth(trace, cdi, vals) {
var span = cdi.max - cdi.min;
// If span is zero
if(!span) {
if(trace.bandwidth) {
return trace.bandwidth;
} else {
// if span is zero and no bandwidth is specified
// it returns zero bandwidth which is a special case
return 0;
}
}
// Limit how small the bandwidth can be.
//
// Silverman's rule of thumb can be "very" small
// when IQR does a poor job at describing the spread
// of the distribution.
// We also want to limit custom bandwidths
// to not blow up kde computations.
if(trace.bandwidth) {
return Math.max(trace.bandwidth, span / 1e4);
} else {
var len = vals.length;
var ssd = Lib.stdev(vals, len - 1, cdi.mean);
return Math.max(
silvermanRule(len, ssd, cdi.q3 - cdi.q1),
span / 100
);
}
}
function calcSpan(trace, cdi, valAxis, bandwidth) {
var spanmode = trace.spanmode;
var spanIn = trace.span || [];
var spanTight = [cdi.min, cdi.max];
var spanLoose = [cdi.min - 2 * bandwidth, cdi.max + 2 * bandwidth];
var spanOut;
function calcSpanItem(index) {
var s = spanIn[index];
var sc = valAxis.type === 'multicategory' ?
valAxis.r2c(s) :
valAxis.d2c(s, 0, trace[cdi.valLetter + 'calendar']);
return sc === BADNUM ? spanLoose[index] : sc;
}
if(spanmode === 'soft') {
spanOut = spanLoose;
} else if(spanmode === 'hard') {
spanOut = spanTight;
} else {
spanOut = [calcSpanItem(0), calcSpanItem(1)];
}
// to reuse the equal-range-item block
var dummyAx = {
type: 'linear',
range: spanOut
};
Axes.setConvert(dummyAx);
dummyAx.cleanRange();
return spanOut;
}
},{"../../constants/numerical":479,"../../lib":503,"../../plots/cartesian/axes":554,"../box/calc":674,"./helpers":1090}],1088:[function(_dereq_,module,exports){
'use strict';
var setPositionOffset = _dereq_('../box/cross_trace_calc').setPositionOffset;
var orientations = ['v', 'h'];
module.exports = function crossTraceCalc(gd, plotinfo) {
var calcdata = gd.calcdata;
var xa = plotinfo.xaxis;
var ya = plotinfo.yaxis;
for(var i = 0; i < orientations.length; i++) {
var orientation = orientations[i];
var posAxis = orientation === 'h' ? ya : xa;
var violinList = [];
for(var j = 0; j < calcdata.length; j++) {
var cd = calcdata[j];
var t = cd[0].t;
var trace = cd[0].trace;
if(trace.visible === true && trace.type === 'violin' &&
!t.empty &&
trace.orientation === orientation &&
trace.xaxis === xa._id &&
trace.yaxis === ya._id
) {
violinList.push(j);
}
}
setPositionOffset('violin', gd, violinList, posAxis);
}
};
},{"../box/cross_trace_calc":675}],1089:[function(_dereq_,module,exports){
'use strict';
var Lib = _dereq_('../../lib');
var Color = _dereq_('../../components/color');
var boxDefaults = _dereq_('../box/defaults');
var attributes = _dereq_('./attributes');
module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout) {
function coerce(attr, dflt) {
return Lib.coerce(traceIn, traceOut, attributes, attr, dflt);
}
function coerce2(attr, dflt) {
return Lib.coerce2(traceIn, traceOut, attributes, attr, dflt);
}
boxDefaults.handleSampleDefaults(traceIn, traceOut, coerce, layout);
if(traceOut.visible === false) return;
coerce('bandwidth');
coerce('side');
var width = coerce('width');
if(!width) {
coerce('scalegroup', traceOut.name);
coerce('scalemode');
}
var span = coerce('span');
var spanmodeDflt;
if(Array.isArray(span)) spanmodeDflt = 'manual';
coerce('spanmode', spanmodeDflt);
var lineColor = coerce('line.color', (traceIn.marker || {}).color || defaultColor);
var lineWidth = coerce('line.width');
var fillColor = coerce('fillcolor', Color.addOpacity(traceOut.line.color, 0.5));
boxDefaults.handlePointsDefaults(traceIn, traceOut, coerce, {prefix: ''});
var boxWidth = coerce2('box.width');
var boxFillColor = coerce2('box.fillcolor', fillColor);
var boxLineColor = coerce2('box.line.color', lineColor);
var boxLineWidth = coerce2('box.line.width', lineWidth);
var boxVisible = coerce('box.visible', Boolean(boxWidth || boxFillColor || boxLineColor || boxLineWidth));
if(!boxVisible) traceOut.box = {visible: false};
var meanLineColor = coerce2('meanline.color', lineColor);
var meanLineWidth = coerce2('meanline.width', lineWidth);
var meanLineVisible = coerce('meanline.visible', Boolean(meanLineColor || meanLineWidth));
if(!meanLineVisible) traceOut.meanline = {visible: false};
};
},{"../../components/color":366,"../../lib":503,"../box/defaults":676,"./attributes":1086}],1090:[function(_dereq_,module,exports){
'use strict';
var Lib = _dereq_('../../lib');
// Maybe add kernels more down the road,
// but note that the default `spanmode: 'soft'` bounds might have
// to become kernel-dependent
var kernels = {
gaussian: function(v) {
return (1 / Math.sqrt(2 * Math.PI)) * Math.exp(-0.5 * v * v);
}
};
exports.makeKDE = function(calcItem, trace, vals) {
var len = vals.length;
var kernel = kernels.gaussian;
var bandwidth = calcItem.bandwidth;
var factor = 1 / (len * bandwidth);
// don't use Lib.aggNums to skip isNumeric checks
return function(x) {
var sum = 0;
for(var i = 0; i < len; i++) {
sum += kernel((x - vals[i]) / bandwidth);
}
return factor * sum;
};
};
exports.getPositionOnKdePath = function(calcItem, trace, valuePx) {
var posLetter, valLetter;
if(trace.orientation === 'h') {
posLetter = 'y';
valLetter = 'x';
} else {
posLetter = 'x';
valLetter = 'y';
}
var pointOnPath = Lib.findPointOnPath(
calcItem.path,
valuePx,
valLetter,
{pathLength: calcItem.pathLength}
);
var posCenterPx = calcItem.posCenterPx;
var posOnPath0 = pointOnPath[posLetter];
var posOnPath1 = trace.side === 'both' ?
2 * posCenterPx - posOnPath0 :
posCenterPx;
return [posOnPath0, posOnPath1];
};
exports.getKdeValue = function(calcItem, trace, valueDist) {
var vals = calcItem.pts.map(exports.extractVal);
var kde = exports.makeKDE(calcItem, trace, vals);
return kde(valueDist) / calcItem.posDensityScale;
};
exports.extractVal = function(o) { return o.v; };
},{"../../lib":503}],1091:[function(_dereq_,module,exports){
'use strict';
var Lib = _dereq_('../../lib');
var Axes = _dereq_('../../plots/cartesian/axes');
var boxHoverPoints = _dereq_('../box/hover');
var helpers = _dereq_('./helpers');
module.exports = function hoverPoints(pointData, xval, yval, hovermode, opts) {
if(!opts) opts = {};
var hoverLayer = opts.hoverLayer;
var cd = pointData.cd;
var trace = cd[0].trace;
var hoveron = trace.hoveron;
var hasHoveronViolins = hoveron.indexOf('violins') !== -1;
var hasHoveronKDE = hoveron.indexOf('kde') !== -1;
var closeData = [];
var closePtData;
var violinLineAttrs;
if(hasHoveronViolins || hasHoveronKDE) {
var closeBoxData = boxHoverPoints.hoverOnBoxes(pointData, xval, yval, hovermode);
if(hasHoveronKDE && closeBoxData.length > 0) {
var xa = pointData.xa;
var ya = pointData.ya;
var pLetter, vLetter, pAxis, vAxis, vVal;
if(trace.orientation === 'h') {
vVal = xval;
pLetter = 'y';
pAxis = ya;
vLetter = 'x';
vAxis = xa;
} else {
vVal = yval;
pLetter = 'x';
pAxis = xa;
vLetter = 'y';
vAxis = ya;
}
var di = cd[pointData.index];
if(vVal >= di.span[0] && vVal <= di.span[1]) {
var kdePointData = Lib.extendFlat({}, pointData);
var vValPx = vAxis.c2p(vVal, true);
var kdeVal = helpers.getKdeValue(di, trace, vVal);
var pOnPath = helpers.getPositionOnKdePath(di, trace, vValPx);
var paOffset = pAxis._offset;
var paLength = pAxis._length;
kdePointData[pLetter + '0'] = pOnPath[0];
kdePointData[pLetter + '1'] = pOnPath[1];
kdePointData[vLetter + '0'] = kdePointData[vLetter + '1'] = vValPx;
kdePointData[vLetter + 'Label'] = vLetter + ': ' + Axes.hoverLabelText(vAxis, vVal, trace[vLetter + 'hoverformat']) + ', ' + cd[0].t.labels.kde + ' ' + kdeVal.toFixed(3);
// move the spike to the KDE point
kdePointData.spikeDistance = closeBoxData[0].spikeDistance;
var spikePosAttr = pLetter + 'Spike';
kdePointData[spikePosAttr] = closeBoxData[0][spikePosAttr];
closeBoxData[0].spikeDistance = undefined;
closeBoxData[0][spikePosAttr] = undefined;
// no hovertemplate support yet
kdePointData.hovertemplate = false;
closeData.push(kdePointData);
violinLineAttrs = {stroke: pointData.color};
violinLineAttrs[pLetter + '1'] = Lib.constrain(paOffset + pOnPath[0], paOffset, paOffset + paLength);
violinLineAttrs[pLetter + '2'] = Lib.constrain(paOffset + pOnPath[1], paOffset, paOffset + paLength);
violinLineAttrs[vLetter + '1'] = violinLineAttrs[vLetter + '2'] = vAxis._offset + vValPx;
}
}
if(hasHoveronViolins) {
closeData = closeData.concat(closeBoxData);
}
}
if(hoveron.indexOf('points') !== -1) {
closePtData = boxHoverPoints.hoverOnPoints(pointData, xval, yval);
}
// update violin line (if any)
var violinLine = hoverLayer.selectAll('.violinline-' + trace.uid)
.data(violinLineAttrs ? [0] : []);
violinLine.enter().append('line')
.classed('violinline-' + trace.uid, true)
.attr('stroke-width', 1.5);
violinLine.exit().remove();
violinLine.attr(violinLineAttrs);
// same combine logic as box hoverPoints
if(hovermode === 'closest') {
if(closePtData) return [closePtData];
return closeData;
}
if(closePtData) {
closeData.push(closePtData);
return closeData;
}
return closeData;
};
},{"../../lib":503,"../../plots/cartesian/axes":554,"../box/hover":678,"./helpers":1090}],1092:[function(_dereq_,module,exports){
'use strict';
module.exports = {
attributes: _dereq_('./attributes'),
layoutAttributes: _dereq_('./layout_attributes'),
supplyDefaults: _dereq_('./defaults'),
crossTraceDefaults: _dereq_('../box/defaults').crossTraceDefaults,
supplyLayoutDefaults: _dereq_('./layout_defaults'),
calc: _dereq_('./calc'),
crossTraceCalc: _dereq_('./cross_trace_calc'),
plot: _dereq_('./plot'),
style: _dereq_('./style'),
styleOnSelect: _dereq_('../scatter/style').styleOnSelect,
hoverPoints: _dereq_('./hover'),
selectPoints: _dereq_('../box/select'),
moduleType: 'trace',
name: 'violin',
basePlotModule: _dereq_('../../plots/cartesian'),
categories: ['cartesian', 'svg', 'symbols', 'oriented', 'box-violin', 'showLegend', 'violinLayout', 'zoomScale'],
meta: {
}
};
},{"../../plots/cartesian":568,"../box/defaults":676,"../box/select":683,"../scatter/style":949,"./attributes":1086,"./calc":1087,"./cross_trace_calc":1088,"./defaults":1089,"./hover":1091,"./layout_attributes":1093,"./layout_defaults":1094,"./plot":1095,"./style":1096}],1093:[function(_dereq_,module,exports){
'use strict';
var boxLayoutAttrs = _dereq_('../box/layout_attributes');
var extendFlat = _dereq_('../../lib').extendFlat;
module.exports = {
violinmode: extendFlat({}, boxLayoutAttrs.boxmode, {
}),
violingap: extendFlat({}, boxLayoutAttrs.boxgap, {
}),
violingroupgap: extendFlat({}, boxLayoutAttrs.boxgroupgap, {
})
};
},{"../../lib":503,"../box/layout_attributes":680}],1094:[function(_dereq_,module,exports){
'use strict';
var Lib = _dereq_('../../lib');
var layoutAttributes = _dereq_('./layout_attributes');
var boxLayoutDefaults = _dereq_('../box/layout_defaults');
module.exports = function supplyLayoutDefaults(layoutIn, layoutOut, fullData) {
function coerce(attr, dflt) {
return Lib.coerce(layoutIn, layoutOut, layoutAttributes, attr, dflt);
}
boxLayoutDefaults._supply(layoutIn, layoutOut, fullData, coerce, 'violin');
};
},{"../../lib":503,"../box/layout_defaults":681,"./layout_attributes":1093}],1095:[function(_dereq_,module,exports){
'use strict';
var d3 = _dereq_('@plotly/d3');
var Lib = _dereq_('../../lib');
var Drawing = _dereq_('../../components/drawing');
var boxPlot = _dereq_('../box/plot');
var linePoints = _dereq_('../scatter/line_points');
var helpers = _dereq_('./helpers');
module.exports = function plot(gd, plotinfo, cdViolins, violinLayer) {
var fullLayout = gd._fullLayout;
var xa = plotinfo.xaxis;
var ya = plotinfo.yaxis;
function makePath(pts) {
var segments = linePoints(pts, {
xaxis: xa,
yaxis: ya,
connectGaps: true,
baseTolerance: 0.75,
shape: 'spline',
simplify: true,
linearized: true
});
return Drawing.smoothopen(segments[0], 1);
}
Lib.makeTraceGroups(violinLayer, cdViolins, 'trace violins').each(function(cd) {
var plotGroup = d3.select(this);
var cd0 = cd[0];
var t = cd0.t;
var trace = cd0.trace;
if(trace.visible !== true || t.empty) {
plotGroup.remove();
return;
}
var bPos = t.bPos;
var bdPos = t.bdPos;
var valAxis = plotinfo[t.valLetter + 'axis'];
var posAxis = plotinfo[t.posLetter + 'axis'];
var hasBothSides = trace.side === 'both';
var hasPositiveSide = hasBothSides || trace.side === 'positive';
var hasNegativeSide = hasBothSides || trace.side === 'negative';
var violins = plotGroup.selectAll('path.violin').data(Lib.identity);
violins.enter().append('path')
.style('vector-effect', 'non-scaling-stroke')
.attr('class', 'violin');
violins.exit().remove();
violins.each(function(d) {
var pathSel = d3.select(this);
var density = d.density;
var len = density.length;
var posCenter = posAxis.c2l(d.pos + bPos, true);
var posCenterPx = posAxis.l2p(posCenter);
var scale;
if(trace.width) {
scale = t.maxKDE / bdPos;
} else {
var groupStats = fullLayout._violinScaleGroupStats[trace.scalegroup];
scale = trace.scalemode === 'count' ?
(groupStats.maxKDE / bdPos) * (groupStats.maxCount / d.pts.length) :
groupStats.maxKDE / bdPos;
}
var pathPos, pathNeg, path;
var i, k, pts, pt;
if(hasPositiveSide) {
pts = new Array(len);
for(i = 0; i < len; i++) {
pt = pts[i] = {};
pt[t.posLetter] = posCenter + (density[i].v / scale);
pt[t.valLetter] = valAxis.c2l(density[i].t, true);
}
pathPos = makePath(pts);
}
if(hasNegativeSide) {
pts = new Array(len);
for(k = 0, i = len - 1; k < len; k++, i--) {
pt = pts[k] = {};
pt[t.posLetter] = posCenter - (density[i].v / scale);
pt[t.valLetter] = valAxis.c2l(density[i].t, true);
}
pathNeg = makePath(pts);
}
if(hasBothSides) {
path = pathPos + 'L' + pathNeg.substr(1) + 'Z';
} else {
var startPt = [posCenterPx, valAxis.c2p(density[0].t)];
var endPt = [posCenterPx, valAxis.c2p(density[len - 1].t)];
if(trace.orientation === 'h') {
startPt.reverse();
endPt.reverse();
}
if(hasPositiveSide) {
path = 'M' + startPt + 'L' + pathPos.substr(1) + 'L' + endPt;
} else {
path = 'M' + endPt + 'L' + pathNeg.substr(1) + 'L' + startPt;
}
}
pathSel.attr('d', path);
// save a few things used in getPositionOnKdePath, getKdeValue
// on hover and for meanline draw block below
d.posCenterPx = posCenterPx;
d.posDensityScale = scale * bdPos;
d.path = pathSel.node();
d.pathLength = d.path.getTotalLength() / (hasBothSides ? 2 : 1);
});
var boxAttrs = trace.box;
var boxWidth = boxAttrs.width;
var boxLineWidth = (boxAttrs.line || {}).width;
var bdPosScaled;
var bPosPxOffset;
if(hasBothSides) {
bdPosScaled = bdPos * boxWidth;
bPosPxOffset = 0;
} else if(hasPositiveSide) {
bdPosScaled = [0, bdPos * boxWidth / 2];
bPosPxOffset = boxLineWidth * {x: 1, y: -1}[t.posLetter];
} else {
bdPosScaled = [bdPos * boxWidth / 2, 0];
bPosPxOffset = boxLineWidth * {x: -1, y: 1}[t.posLetter];
}
// inner box
boxPlot.plotBoxAndWhiskers(plotGroup, {pos: posAxis, val: valAxis}, trace, {
bPos: bPos,
bdPos: bdPosScaled,
bPosPxOffset: bPosPxOffset
});
// meanline insider box
boxPlot.plotBoxMean(plotGroup, {pos: posAxis, val: valAxis}, trace, {
bPos: bPos,
bdPos: bdPosScaled,
bPosPxOffset: bPosPxOffset
});
var fn;
if(!trace.box.visible && trace.meanline.visible) {
fn = Lib.identity;
}
// N.B. use different class name than boxPlot.plotBoxMean,
// to avoid selectAll conflict
var meanPaths = plotGroup.selectAll('path.meanline').data(fn || []);
meanPaths.enter().append('path')
.attr('class', 'meanline')
.style('fill', 'none')
.style('vector-effect', 'non-scaling-stroke');
meanPaths.exit().remove();
meanPaths.each(function(d) {
var v = valAxis.c2p(d.mean, true);
var p = helpers.getPositionOnKdePath(d, trace, v);
d3.select(this).attr('d',
trace.orientation === 'h' ?
'M' + v + ',' + p[0] + 'V' + p[1] :
'M' + p[0] + ',' + v + 'H' + p[1]
);
});
boxPlot.plotPoints(plotGroup, {x: xa, y: ya}, trace, t);
});
};
},{"../../components/drawing":388,"../../lib":503,"../box/plot":682,"../scatter/line_points":939,"./helpers":1090,"@plotly/d3":58}],1096:[function(_dereq_,module,exports){
'use strict';
var d3 = _dereq_('@plotly/d3');
var Color = _dereq_('../../components/color');
var stylePoints = _dereq_('../scatter/style').stylePoints;
module.exports = function style(gd) {
var s = d3.select(gd).selectAll('g.trace.violins');
s.style('opacity', function(d) { return d[0].trace.opacity; });
s.each(function(d) {
var trace = d[0].trace;
var sel = d3.select(this);
var box = trace.box || {};
var boxLine = box.line || {};
var meanline = trace.meanline || {};
var meanLineWidth = meanline.width;
sel.selectAll('path.violin')
.style('stroke-width', trace.line.width + 'px')
.call(Color.stroke, trace.line.color)
.call(Color.fill, trace.fillcolor);
sel.selectAll('path.box')
.style('stroke-width', boxLine.width + 'px')
.call(Color.stroke, boxLine.color)
.call(Color.fill, box.fillcolor);
var meanLineStyle = {
'stroke-width': meanLineWidth + 'px',
'stroke-dasharray': (2 * meanLineWidth) + 'px,' + meanLineWidth + 'px'
};
sel.selectAll('path.mean')
.style(meanLineStyle)
.call(Color.stroke, meanline.color);
sel.selectAll('path.meanline')
.style(meanLineStyle)
.call(Color.stroke, meanline.color);
stylePoints(sel, trace, gd);
});
};
},{"../../components/color":366,"../scatter/style":949,"@plotly/d3":58}],1097:[function(_dereq_,module,exports){
'use strict';
var colorScaleAttrs = _dereq_('../../components/colorscale/attributes');
var isosurfaceAttrs = _dereq_('../isosurface/attributes');
var surfaceAttrs = _dereq_('../surface/attributes');
var baseAttrs = _dereq_('../../plots/attributes');
var extendFlat = _dereq_('../../lib/extend').extendFlat;
var overrideAll = _dereq_('../../plot_api/edit_types').overrideAll;
var attrs = module.exports = overrideAll(extendFlat({
x: isosurfaceAttrs.x,
y: isosurfaceAttrs.y,
z: isosurfaceAttrs.z,
value: isosurfaceAttrs.value,
isomin: isosurfaceAttrs.isomin,
isomax: isosurfaceAttrs.isomax,
surface: isosurfaceAttrs.surface,
spaceframe: {
show: {
valType: 'boolean',
dflt: false,
},
fill: {
valType: 'number',
min: 0,
max: 1,
dflt: 1,
}
},
slices: isosurfaceAttrs.slices,
caps: isosurfaceAttrs.caps,
text: isosurfaceAttrs.text,
hovertext: isosurfaceAttrs.hovertext,
xhoverformat: isosurfaceAttrs.xhoverformat,
yhoverformat: isosurfaceAttrs.yhoverformat,
zhoverformat: isosurfaceAttrs.zhoverformat,
valuehoverformat: isosurfaceAttrs.valuehoverformat,
hovertemplate: isosurfaceAttrs.hovertemplate
},
colorScaleAttrs('', {
colorAttr: '`value`',
showScaleDflt: true,
editTypeOverride: 'calc'
}), {
colorbar: isosurfaceAttrs.colorbar,
opacity: isosurfaceAttrs.opacity,
opacityscale: surfaceAttrs.opacityscale,
lightposition: isosurfaceAttrs.lightposition,
lighting: isosurfaceAttrs.lighting,
flatshading: isosurfaceAttrs.flatshading,
contour: isosurfaceAttrs.contour,
hoverinfo: extendFlat({}, baseAttrs.hoverinfo),
showlegend: extendFlat({}, baseAttrs.showlegend, {dflt: false})
}), 'calc', 'nested');
attrs.x.editType = attrs.y.editType = attrs.z.editType = attrs.value.editType = 'calc+clearAxisTypes';
attrs.transforms = undefined;
},{"../../components/colorscale/attributes":373,"../../lib/extend":493,"../../plot_api/edit_types":536,"../../plots/attributes":550,"../isosurface/attributes":861,"../surface/attributes":1056}],1098:[function(_dereq_,module,exports){
'use strict';
var createMesh = _dereq_('../../../stackgl_modules').gl_mesh3d;
var parseColorScale = _dereq_('../../lib/gl_format_color').parseColorScale;
var str2RgbaArray = _dereq_('../../lib/str2rgbarray');
var extractOpts = _dereq_('../../components/colorscale').extractOpts;
var zip3 = _dereq_('../../plots/gl3d/zip3');
var findNearestOnAxis = _dereq_('../isosurface/convert').findNearestOnAxis;
var generateIsoMeshes = _dereq_('../isosurface/convert').generateIsoMeshes;
function VolumeTrace(scene, mesh, uid) {
this.scene = scene;
this.uid = uid;
this.mesh = mesh;
this.name = '';
this.data = null;
this.showContour = false;
}
var proto = VolumeTrace.prototype;
proto.handlePick = function(selection) {
if(selection.object === this.mesh) {
var rawId = selection.data.index;
var x = this.data._meshX[rawId];
var y = this.data._meshY[rawId];
var z = this.data._meshZ[rawId];
var height = this.data._Ys.length;
var depth = this.data._Zs.length;
var i = findNearestOnAxis(x, this.data._Xs).id;
var j = findNearestOnAxis(y, this.data._Ys).id;
var k = findNearestOnAxis(z, this.data._Zs).id;
var selectIndex = selection.index = k + depth * j + depth * height * i;
selection.traceCoordinate = [
this.data._meshX[selectIndex],
this.data._meshY[selectIndex],
this.data._meshZ[selectIndex],
this.data._value[selectIndex]
];
var text = this.data.hovertext || this.data.text;
if(Array.isArray(text) && text[selectIndex] !== undefined) {
selection.textLabel = text[selectIndex];
} else if(text) {
selection.textLabel = text;
}
return true;
}
};
proto.update = function(data) {
var scene = this.scene;
var layout = scene.fullSceneLayout;
this.data = generateIsoMeshes(data);
// Unpack position data
function toDataCoords(axis, coord, scale, calendar) {
return coord.map(function(x) {
return axis.d2l(x, 0, calendar) * scale;
});
}
var positions = zip3(
toDataCoords(layout.xaxis, data._meshX, scene.dataScale[0], data.xcalendar),
toDataCoords(layout.yaxis, data._meshY, scene.dataScale[1], data.ycalendar),
toDataCoords(layout.zaxis, data._meshZ, scene.dataScale[2], data.zcalendar));
var cells = zip3(data._meshI, data._meshJ, data._meshK);
var config = {
positions: positions,
cells: cells,
lightPosition: [data.lightposition.x, data.lightposition.y, data.lightposition.z],
ambient: data.lighting.ambient,
diffuse: data.lighting.diffuse,
specular: data.lighting.specular,
roughness: data.lighting.roughness,
fresnel: data.lighting.fresnel,
vertexNormalsEpsilon: data.lighting.vertexnormalsepsilon,
faceNormalsEpsilon: data.lighting.facenormalsepsilon,
opacity: data.opacity,
opacityscale: data.opacityscale,
contourEnable: data.contour.show,
contourColor: str2RgbaArray(data.contour.color).slice(0, 3),
contourWidth: data.contour.width,
useFacetNormals: data.flatshading
};
var cOpts = extractOpts(data);
config.vertexIntensity = data._meshIntensity;
config.vertexIntensityBounds = [cOpts.min, cOpts.max];
config.colormap = parseColorScale(data);
// Update mesh
this.mesh.update(config);
};
proto.dispose = function() {
this.scene.glplot.remove(this.mesh);
this.mesh.dispose();
};
function createVolumeTrace(scene, data) {
var gl = scene.glplot.gl;
var mesh = createMesh({gl: gl});
var result = new VolumeTrace(scene, mesh, data.uid);
mesh._trace = result;
result.update(data);
scene.glplot.add(mesh);
return result;
}
module.exports = createVolumeTrace;
},{"../../../stackgl_modules":1119,"../../components/colorscale":378,"../../lib/gl_format_color":499,"../../lib/str2rgbarray":528,"../../plots/gl3d/zip3":609,"../isosurface/convert":863}],1099:[function(_dereq_,module,exports){
'use strict';
var Lib = _dereq_('../../lib');
var attributes = _dereq_('./attributes');
var supplyIsoDefaults = _dereq_('../isosurface/defaults').supplyIsoDefaults;
var opacityscaleDefaults = _dereq_('../surface/defaults').opacityscaleDefaults;
module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout) {
function coerce(attr, dflt) {
return Lib.coerce(traceIn, traceOut, attributes, attr, dflt);
}
supplyIsoDefaults(traceIn, traceOut, defaultColor, layout, coerce);
opacityscaleDefaults(traceIn, traceOut, layout, coerce);
};
},{"../../lib":503,"../isosurface/defaults":864,"../surface/defaults":1059,"./attributes":1097}],1100:[function(_dereq_,module,exports){
'use strict';
module.exports = {
attributes: _dereq_('./attributes'),
supplyDefaults: _dereq_('./defaults'),
calc: _dereq_('../isosurface/calc'),
colorbar: {
min: 'cmin',
max: 'cmax'
},
plot: _dereq_('./convert'),
moduleType: 'trace',
name: 'volume',
basePlotModule: _dereq_('../../plots/gl3d'),
categories: ['gl3d', 'showLegend'],
meta: {
}
};
},{"../../plots/gl3d":598,"../isosurface/calc":862,"./attributes":1097,"./convert":1098,"./defaults":1099}],1101:[function(_dereq_,module,exports){
'use strict';
var barAttrs = _dereq_('../bar/attributes');
var lineAttrs = _dereq_('../scatter/attributes').line;
var baseAttrs = _dereq_('../../plots/attributes');
var axisHoverFormat = _dereq_('../../plots/cartesian/axis_format_attributes').axisHoverFormat;
var hovertemplateAttrs = _dereq_('../../plots/template_attributes').hovertemplateAttrs;
var texttemplateAttrs = _dereq_('../../plots/template_attributes').texttemplateAttrs;
var constants = _dereq_('./constants');
var extendFlat = _dereq_('../../lib/extend').extendFlat;
var Color = _dereq_('../../components/color');
function directionAttrs(dirTxt) {
return {
marker: {
color: extendFlat({}, barAttrs.marker.color, {
arrayOk: false,
editType: 'style',
}),
line: {
color: extendFlat({}, barAttrs.marker.line.color, {
arrayOk: false,
editType: 'style',
}),
width: extendFlat({}, barAttrs.marker.line.width, {
arrayOk: false,
editType: 'style',
}),
editType: 'style',
},
editType: 'style'
},
editType: 'style'
};
}
module.exports = {
measure: {
valType: 'data_array',
dflt: [],
editType: 'calc',
},
base: {
valType: 'number',
dflt: null,
arrayOk: false,
editType: 'calc',
},
x: barAttrs.x,
x0: barAttrs.x0,
dx: barAttrs.dx,
y: barAttrs.y,
y0: barAttrs.y0,
dy: barAttrs.dy,
xperiod: barAttrs.xperiod,
yperiod: barAttrs.yperiod,
xperiod0: barAttrs.xperiod0,
yperiod0: barAttrs.yperiod0,
xperiodalignment: barAttrs.xperiodalignment,
yperiodalignment: barAttrs.yperiodalignment,
xhoverformat: axisHoverFormat('x'),
yhoverformat: axisHoverFormat('y'),
hovertext: barAttrs.hovertext,
hovertemplate: hovertemplateAttrs({}, {
keys: constants.eventDataKeys
}),
hoverinfo: extendFlat({}, baseAttrs.hoverinfo, {
flags: ['name', 'x', 'y', 'text', 'initial', 'delta', 'final']
}),
textinfo: {
valType: 'flaglist',
flags: ['label', 'text', 'initial', 'delta', 'final'],
extras: ['none'],
editType: 'plot',
arrayOk: false,
},
// TODO: incorporate `label` and `value` in the eventData
texttemplate: texttemplateAttrs({editType: 'plot'}, {
keys: constants.eventDataKeys.concat(['label'])
}),
text: barAttrs.text,
textposition: barAttrs.textposition,
insidetextanchor: barAttrs.insidetextanchor,
textangle: barAttrs.textangle,
textfont: barAttrs.textfont,
insidetextfont: barAttrs.insidetextfont,
outsidetextfont: barAttrs.outsidetextfont,
constraintext: barAttrs.constraintext,
cliponaxis: barAttrs.cliponaxis,
orientation: barAttrs.orientation,
offset: barAttrs.offset,
width: barAttrs.width,
increasing: directionAttrs('increasing'),
decreasing: directionAttrs('decreasing'),
totals: directionAttrs('intermediate sums and total'),
connector: {
line: {
color: extendFlat({}, lineAttrs.color, {dflt: Color.defaultLine}),
width: extendFlat({}, lineAttrs.width, {
editType: 'plot', // i.e. to adjust bars is mode: 'between'. See https://github.com/plotly/plotly.js/issues/3787
}),
dash: lineAttrs.dash,
editType: 'plot'
},
mode: {
valType: 'enumerated',
values: ['spanning', 'between'],
dflt: 'between',
editType: 'plot',
},
visible: {
valType: 'boolean',
dflt: true,
editType: 'plot',
},
editType: 'plot'
},
offsetgroup: barAttrs.offsetgroup,
alignmentgroup: barAttrs.alignmentgroup
};
},{"../../components/color":366,"../../lib/extend":493,"../../plots/attributes":550,"../../plots/cartesian/axis_format_attributes":557,"../../plots/template_attributes":633,"../bar/attributes":648,"../scatter/attributes":925,"./constants":1103}],1102:[function(_dereq_,module,exports){
'use strict';
var Axes = _dereq_('../../plots/cartesian/axes');
var alignPeriod = _dereq_('../../plots/cartesian/align_period');
var mergeArray = _dereq_('../../lib').mergeArray;
var calcSelection = _dereq_('../scatter/calc_selection');
var BADNUM = _dereq_('../../constants/numerical').BADNUM;
function isAbsolute(a) {
return (a === 'a' || a === 'absolute');
}
function isTotal(a) {
return (a === 't' || a === 'total');
}
module.exports = function calc(gd, trace) {
var xa = Axes.getFromId(gd, trace.xaxis || 'x');
var ya = Axes.getFromId(gd, trace.yaxis || 'y');
var size, pos, origPos, pObj, hasPeriod, pLetter;
if(trace.orientation === 'h') {
size = xa.makeCalcdata(trace, 'x');
origPos = ya.makeCalcdata(trace, 'y');
pObj = alignPeriod(trace, ya, 'y', origPos);
hasPeriod = !!trace.yperiodalignment;
pLetter = 'y';
} else {
size = ya.makeCalcdata(trace, 'y');
origPos = xa.makeCalcdata(trace, 'x');
pObj = alignPeriod(trace, xa, 'x', origPos);
hasPeriod = !!trace.xperiodalignment;
pLetter = 'x';
}
pos = pObj.vals;
// create the "calculated data" to plot
var serieslen = Math.min(pos.length, size.length);
var cd = new Array(serieslen);
// set position and size (as well as for waterfall total size)
var previousSum = 0;
var newSize;
// trace-wide flags
var hasTotals = false;
for(var i = 0; i < serieslen; i++) {
var amount = size[i] || 0;
var connectToNext = false;
if(size[i] !== BADNUM || isTotal(trace.measure[i]) || isAbsolute(trace.measure[i])) {
if(i + 1 < serieslen && (size[i + 1] !== BADNUM || isTotal(trace.measure[i + 1]) || isAbsolute(trace.measure[i + 1]))) {
connectToNext = true;
}
}
var cdi = cd[i] = {
i: i,
p: pos[i],
s: amount,
rawS: amount,
cNext: connectToNext
};
if(isAbsolute(trace.measure[i])) {
previousSum = cdi.s;
cdi.isSum = true;
cdi.dir = 'totals';
cdi.s = previousSum;
} else if(isTotal(trace.measure[i])) {
cdi.isSum = true;
cdi.dir = 'totals';
cdi.s = previousSum;
} else {
// default: relative
cdi.isSum = false;
cdi.dir = cdi.rawS < 0 ? 'decreasing' : 'increasing';
newSize = cdi.s;
cdi.s = previousSum + newSize;
previousSum += newSize;
}
if(cdi.dir === 'totals') {
hasTotals = true;
}
if(hasPeriod) {
cd[i].orig_p = origPos[i]; // used by hover
cd[i][pLetter + 'End'] = pObj.ends[i];
cd[i][pLetter + 'Start'] = pObj.starts[i];
}
if(trace.ids) {
cdi.id = String(trace.ids[i]);
}
cdi.v = (trace.base || 0) + previousSum;
}
if(cd.length) cd[0].hasTotals = hasTotals;
mergeArray(trace.text, cd, 'tx');
mergeArray(trace.hovertext, cd, 'htx');
calcSelection(cd, trace);
return cd;
};
},{"../../constants/numerical":479,"../../lib":503,"../../plots/cartesian/align_period":551,"../../plots/cartesian/axes":554,"../scatter/calc_selection":927}],1103:[function(_dereq_,module,exports){
'use strict';
module.exports = {
eventDataKeys: [
'initial',
'delta',
'final'
]
};
},{}],1104:[function(_dereq_,module,exports){
'use strict';
var setGroupPositions = _dereq_('../bar/cross_trace_calc').setGroupPositions;
module.exports = function crossTraceCalc(gd, plotinfo) {
var fullLayout = gd._fullLayout;
var fullData = gd._fullData;
var calcdata = gd.calcdata;
var xa = plotinfo.xaxis;
var ya = plotinfo.yaxis;
var waterfalls = [];
var waterfallsVert = [];
var waterfallsHorz = [];
var cd, i;
for(i = 0; i < fullData.length; i++) {
var fullTrace = fullData[i];
if(
fullTrace.visible === true &&
fullTrace.xaxis === xa._id &&
fullTrace.yaxis === ya._id &&
fullTrace.type === 'waterfall'
) {
cd = calcdata[i];
if(fullTrace.orientation === 'h') {
waterfallsHorz.push(cd);
} else {
waterfallsVert.push(cd);
}
waterfalls.push(cd);
}
}
var opts = {
mode: fullLayout.waterfallmode,
norm: fullLayout.waterfallnorm,
gap: fullLayout.waterfallgap,
groupgap: fullLayout.waterfallgroupgap
};
setGroupPositions(gd, xa, ya, waterfallsVert, opts);
setGroupPositions(gd, ya, xa, waterfallsHorz, opts);
for(i = 0; i < waterfalls.length; i++) {
cd = waterfalls[i];
for(var j = 0; j < cd.length; j++) {
var di = cd[j];
if(di.isSum === false) {
di.s0 += (j === 0) ? 0 : cd[j - 1].s;
}
if(j + 1 < cd.length) {
cd[j].nextP0 = cd[j + 1].p0;
cd[j].nextS0 = cd[j + 1].s0;
}
}
}
};
},{"../bar/cross_trace_calc":651}],1105:[function(_dereq_,module,exports){
'use strict';
var Lib = _dereq_('../../lib');
var handleGroupingDefaults = _dereq_('../bar/defaults').handleGroupingDefaults;
var handleText = _dereq_('../bar/defaults').handleText;
var handleXYDefaults = _dereq_('../scatter/xy_defaults');
var handlePeriodDefaults = _dereq_('../scatter/period_defaults');
var attributes = _dereq_('./attributes');
var Color = _dereq_('../../components/color');
var delta = _dereq_('../../constants/delta.js');
var INCREASING_COLOR = delta.INCREASING.COLOR;
var DECREASING_COLOR = delta.DECREASING.COLOR;
var TOTALS_COLOR = '#4499FF';
function handleDirection(coerce, direction, defaultColor) {
coerce(direction + '.marker.color', defaultColor);
coerce(direction + '.marker.line.color', Color.defaultLine);
coerce(direction + '.marker.line.width');
}
function supplyDefaults(traceIn, traceOut, defaultColor, layout) {
function coerce(attr, dflt) {
return Lib.coerce(traceIn, traceOut, attributes, attr, dflt);
}
var len = handleXYDefaults(traceIn, traceOut, layout, coerce);
if(!len) {
traceOut.visible = false;
return;
}
handlePeriodDefaults(traceIn, traceOut, layout, coerce);
coerce('xhoverformat');
coerce('yhoverformat');
coerce('measure');
coerce('orientation', (traceOut.x && !traceOut.y) ? 'h' : 'v');
coerce('base');
coerce('offset');
coerce('width');
coerce('text');
coerce('hovertext');
coerce('hovertemplate');
var textposition = coerce('textposition');
handleText(traceIn, traceOut, layout, coerce, textposition, {
moduleHasSelected: false,
moduleHasUnselected: false,
moduleHasConstrain: true,
moduleHasCliponaxis: true,
moduleHasTextangle: true,
moduleHasInsideanchor: true
});
if(traceOut.textposition !== 'none') {
coerce('texttemplate');
if(!traceOut.texttemplate) coerce('textinfo');
}
handleDirection(coerce, 'increasing', INCREASING_COLOR);
handleDirection(coerce, 'decreasing', DECREASING_COLOR);
handleDirection(coerce, 'totals', TOTALS_COLOR);
var connectorVisible = coerce('connector.visible');
if(connectorVisible) {
coerce('connector.mode');
var connectorLineWidth = coerce('connector.line.width');
if(connectorLineWidth) {
coerce('connector.line.color');
coerce('connector.line.dash');
}
}
}
function crossTraceDefaults(fullData, fullLayout) {
var traceIn, traceOut;
function coerce(attr) {
return Lib.coerce(traceOut._input, traceOut, attributes, attr);
}
if(fullLayout.waterfallmode === 'group') {
for(var i = 0; i < fullData.length; i++) {
traceOut = fullData[i];
traceIn = traceOut._input;
handleGroupingDefaults(traceIn, traceOut, fullLayout, coerce);
}
}
}
module.exports = {
supplyDefaults: supplyDefaults,
crossTraceDefaults: crossTraceDefaults
};
},{"../../components/color":366,"../../constants/delta.js":473,"../../lib":503,"../bar/defaults":652,"../scatter/period_defaults":945,"../scatter/xy_defaults":952,"./attributes":1101}],1106:[function(_dereq_,module,exports){
'use strict';
module.exports = function eventData(out, pt /* , trace, cd, pointNumber */) {
// standard cartesian event data
out.x = 'xVal' in pt ? pt.xVal : pt.x;
out.y = 'yVal' in pt ? pt.yVal : pt.y;
// for funnel
if('initial' in pt) out.initial = pt.initial;
if('delta' in pt) out.delta = pt.delta;
if('final' in pt) out.final = pt.final;
if(pt.xa) out.xaxis = pt.xa;
if(pt.ya) out.yaxis = pt.ya;
return out;
};
},{}],1107:[function(_dereq_,module,exports){
'use strict';
var hoverLabelText = _dereq_('../../plots/cartesian/axes').hoverLabelText;
var opacity = _dereq_('../../components/color').opacity;
var hoverOnBars = _dereq_('../bar/hover').hoverOnBars;
var delta = _dereq_('../../constants/delta.js');
var DIRSYMBOL = {
increasing: delta.INCREASING.SYMBOL,
decreasing: delta.DECREASING.SYMBOL
};
module.exports = function hoverPoints(pointData, xval, yval, hovermode, opts) {
var point = hoverOnBars(pointData, xval, yval, hovermode, opts);
if(!point) return;
var cd = point.cd;
var trace = cd[0].trace;
var isHorizontal = (trace.orientation === 'h');
var vLetter = isHorizontal ? 'x' : 'y';
var vAxis = isHorizontal ? pointData.xa : pointData.ya;
function formatNumber(a) {
return hoverLabelText(vAxis, a, trace[vLetter + 'hoverformat']);
}
// the closest data point
var index = point.index;
var di = cd[index];
var size = (di.isSum) ? di.b + di.s : di.rawS;
if(!di.isSum) {
point.initial = di.b + di.s - size;
point.delta = size;
point.final = point.initial + point.delta;
var v = formatNumber(Math.abs(point.delta));
point.deltaLabel = size < 0 ? '(' + v + ')' : v;
point.finalLabel = formatNumber(point.final);
point.initialLabel = formatNumber(point.initial);
}
var hoverinfo = di.hi || trace.hoverinfo;
var text = [];
if(hoverinfo && hoverinfo !== 'none' && hoverinfo !== 'skip') {
var isAll = (hoverinfo === 'all');
var parts = hoverinfo.split('+');
var hasFlag = function(flag) { return isAll || parts.indexOf(flag) !== -1; };
if(!di.isSum) {
if(hasFlag('final') &&
(isHorizontal ? !hasFlag('x') : !hasFlag('y')) // don't display redundant info.
) {
text.push(point.finalLabel);
}
if(hasFlag('delta')) {
if(size < 0) {
text.push(point.deltaLabel + ' ' + DIRSYMBOL.decreasing);
} else {
text.push(point.deltaLabel + ' ' + DIRSYMBOL.increasing);
}
}
if(hasFlag('initial')) {
text.push('Initial: ' + point.initialLabel);
}
}
}
if(text.length) point.extraText = text.join('
');
point.color = getTraceColor(trace, di);
return [point];
};
function getTraceColor(trace, di) {
var cont = trace[di.dir].marker;
var mc = cont.color;
var mlc = cont.line.color;
var mlw = cont.line.width;
if(opacity(mc)) return mc;
else if(opacity(mlc) && mlw) return mlc;
}
},{"../../components/color":366,"../../constants/delta.js":473,"../../plots/cartesian/axes":554,"../bar/hover":655}],1108:[function(_dereq_,module,exports){
'use strict';
module.exports = {
attributes: _dereq_('./attributes'),
layoutAttributes: _dereq_('./layout_attributes'),
supplyDefaults: _dereq_('./defaults').supplyDefaults,
crossTraceDefaults: _dereq_('./defaults').crossTraceDefaults,
supplyLayoutDefaults: _dereq_('./layout_defaults'),
calc: _dereq_('./calc'),
crossTraceCalc: _dereq_('./cross_trace_calc'),
plot: _dereq_('./plot'),
style: _dereq_('./style').style,
hoverPoints: _dereq_('./hover'),
eventData: _dereq_('./event_data'),
selectPoints: _dereq_('../bar/select'),
moduleType: 'trace',
name: 'waterfall',
basePlotModule: _dereq_('../../plots/cartesian'),
categories: ['bar-like', 'cartesian', 'svg', 'oriented', 'showLegend', 'zoomScale'],
meta: {
}
};
},{"../../plots/cartesian":568,"../bar/select":660,"./attributes":1101,"./calc":1102,"./cross_trace_calc":1104,"./defaults":1105,"./event_data":1106,"./hover":1107,"./layout_attributes":1109,"./layout_defaults":1110,"./plot":1111,"./style":1112}],1109:[function(_dereq_,module,exports){
'use strict';
module.exports = {
waterfallmode: {
valType: 'enumerated',
values: ['group', 'overlay'],
dflt: 'group',
editType: 'calc',
},
waterfallgap: {
valType: 'number',
min: 0,
max: 1,
editType: 'calc',
},
waterfallgroupgap: {
valType: 'number',
min: 0,
max: 1,
dflt: 0,
editType: 'calc',
}
};
},{}],1110:[function(_dereq_,module,exports){
'use strict';
var Lib = _dereq_('../../lib');
var layoutAttributes = _dereq_('./layout_attributes');
module.exports = function(layoutIn, layoutOut, fullData) {
var hasTraceType = false;
function coerce(attr, dflt) {
return Lib.coerce(layoutIn, layoutOut, layoutAttributes, attr, dflt);
}
for(var i = 0; i < fullData.length; i++) {
var trace = fullData[i];
if(trace.visible && trace.type === 'waterfall') {
hasTraceType = true;
break;
}
}
if(hasTraceType) {
coerce('waterfallmode');
coerce('waterfallgap', 0.2);
coerce('waterfallgroupgap');
}
};
},{"../../lib":503,"./layout_attributes":1109}],1111:[function(_dereq_,module,exports){
'use strict';
var d3 = _dereq_('@plotly/d3');
var Lib = _dereq_('../../lib');
var Drawing = _dereq_('../../components/drawing');
var BADNUM = _dereq_('../../constants/numerical').BADNUM;
var barPlot = _dereq_('../bar/plot');
var clearMinTextSize = _dereq_('../bar/uniform_text').clearMinTextSize;
module.exports = function plot(gd, plotinfo, cdModule, traceLayer) {
var fullLayout = gd._fullLayout;
clearMinTextSize('waterfall', fullLayout);
barPlot.plot(gd, plotinfo, cdModule, traceLayer, {
mode: fullLayout.waterfallmode,
norm: fullLayout.waterfallmode,
gap: fullLayout.waterfallgap,
groupgap: fullLayout.waterfallgroupgap
});
plotConnectors(gd, plotinfo, cdModule, traceLayer);
};
function plotConnectors(gd, plotinfo, cdModule, traceLayer) {
var xa = plotinfo.xaxis;
var ya = plotinfo.yaxis;
Lib.makeTraceGroups(traceLayer, cdModule, 'trace bars').each(function(cd) {
var plotGroup = d3.select(this);
var trace = cd[0].trace;
var group = Lib.ensureSingle(plotGroup, 'g', 'lines');
if(!trace.connector || !trace.connector.visible) {
group.remove();
return;
}
var isHorizontal = (trace.orientation === 'h');
var mode = trace.connector.mode;
var connectors = group.selectAll('g.line').data(Lib.identity);
connectors.enter().append('g')
.classed('line', true);
connectors.exit().remove();
var len = connectors.size();
connectors.each(function(di, i) {
// don't draw lines between nulls
if(i !== len - 1 && !di.cNext) return;
var xy = getXY(di, xa, ya, isHorizontal);
var x = xy[0];
var y = xy[1];
var shape = '';
if(
x[0] !== BADNUM && y[0] !== BADNUM &&
x[1] !== BADNUM && y[1] !== BADNUM
) {
if(mode === 'spanning') {
if(!di.isSum && i > 0) {
if(isHorizontal) {
shape += 'M' + x[0] + ',' + y[1] + 'V' + y[0];
} else {
shape += 'M' + x[1] + ',' + y[0] + 'H' + x[0];
}
}
}
if(mode !== 'between') {
if(di.isSum || i < len - 1) {
if(isHorizontal) {
shape += 'M' + x[1] + ',' + y[0] + 'V' + y[1];
} else {
shape += 'M' + x[0] + ',' + y[1] + 'H' + x[1];
}
}
}
if(x[2] !== BADNUM && y[2] !== BADNUM) {
if(isHorizontal) {
shape += 'M' + x[1] + ',' + y[1] + 'V' + y[2];
} else {
shape += 'M' + x[1] + ',' + y[1] + 'H' + x[2];
}
}
}
if(shape === '') shape = 'M0,0Z';
Lib.ensureSingle(d3.select(this), 'path')
.attr('d', shape)
.call(Drawing.setClipUrl, plotinfo.layerClipId, gd);
});
});
}
function getXY(di, xa, ya, isHorizontal) {
var s = [];
var p = [];
var sAxis = isHorizontal ? xa : ya;
var pAxis = isHorizontal ? ya : xa;
s[0] = sAxis.c2p(di.s0, true);
p[0] = pAxis.c2p(di.p0, true);
s[1] = sAxis.c2p(di.s1, true);
p[1] = pAxis.c2p(di.p1, true);
s[2] = sAxis.c2p(di.nextS0, true);
p[2] = pAxis.c2p(di.nextP0, true);
return isHorizontal ? [s, p] : [p, s];
}
},{"../../components/drawing":388,"../../constants/numerical":479,"../../lib":503,"../bar/plot":659,"../bar/uniform_text":664,"@plotly/d3":58}],1112:[function(_dereq_,module,exports){
'use strict';
var d3 = _dereq_('@plotly/d3');
var Drawing = _dereq_('../../components/drawing');
var Color = _dereq_('../../components/color');
var DESELECTDIM = _dereq_('../../constants/interactions').DESELECTDIM;
var barStyle = _dereq_('../bar/style');
var resizeText = _dereq_('../bar/uniform_text').resizeText;
var styleTextPoints = barStyle.styleTextPoints;
function style(gd, cd, sel) {
var s = sel ? sel : d3.select(gd).selectAll('g.waterfalllayer').selectAll('g.trace');
resizeText(gd, s, 'waterfall');
s.style('opacity', function(d) { return d[0].trace.opacity; });
s.each(function(d) {
var gTrace = d3.select(this);
var trace = d[0].trace;
gTrace.selectAll('.point > path').each(function(di) {
if(!di.isBlank) {
var cont = trace[di.dir].marker;
d3.select(this)
.call(Color.fill, cont.color)
.call(Color.stroke, cont.line.color)
.call(Drawing.dashLine, cont.line.dash, cont.line.width)
.style('opacity', trace.selectedpoints && !di.selected ? DESELECTDIM : 1);
}
});
styleTextPoints(gTrace, trace, gd);
gTrace.selectAll('.lines').each(function() {
var cont = trace.connector.line;
Drawing.lineGroupStyle(
d3.select(this).selectAll('path'),
cont.width,
cont.color,
cont.dash
);
});
});
}
module.exports = {
style: style
};
},{"../../components/color":366,"../../components/drawing":388,"../../constants/interactions":478,"../bar/style":662,"../bar/uniform_text":664,"@plotly/d3":58}],1113:[function(_dereq_,module,exports){
'use strict';
var Axes = _dereq_('../plots/cartesian/axes');
var Lib = _dereq_('../lib');
var PlotSchema = _dereq_('../plot_api/plot_schema');
var pointsAccessorFunction = _dereq_('./helpers').pointsAccessorFunction;
var BADNUM = _dereq_('../constants/numerical').BADNUM;
exports.moduleType = 'transform';
exports.name = 'aggregate';
var attrs = exports.attributes = {
enabled: {
valType: 'boolean',
dflt: true,
editType: 'calc',
},
groups: {
// TODO: groupby should support string or array grouping this way too
// currently groupby only allows a grouping array
valType: 'string',
strict: true,
noBlank: true,
arrayOk: true,
dflt: 'x',
editType: 'calc',
},
aggregations: {
_isLinkedToArray: 'aggregation',
target: {
valType: 'string',
editType: 'calc',
},
func: {
valType: 'enumerated',
values: ['count', 'sum', 'avg', 'median', 'mode', 'rms', 'stddev', 'min', 'max', 'first', 'last', 'change', 'range'],
dflt: 'first',
editType: 'calc',
},
funcmode: {
valType: 'enumerated',
values: ['sample', 'population'],
dflt: 'sample',
editType: 'calc',
},
enabled: {
valType: 'boolean',
dflt: true,
editType: 'calc',
},
editType: 'calc'
},
editType: 'calc'
};
var aggAttrs = attrs.aggregations;
/**
* Supply transform attributes defaults
*
* @param {object} transformIn
* object linked to trace.transforms[i] with 'func' set to exports.name
* @param {object} traceOut
* the _fullData trace this transform applies to
* @param {object} layout
* the plot's (not-so-full) layout
* @param {object} traceIn
* the input data trace this transform applies to
*
* @return {object} transformOut
* copy of transformIn that contains attribute defaults
*/
exports.supplyDefaults = function(transformIn, traceOut) {
var transformOut = {};
var i;
function coerce(attr, dflt) {
return Lib.coerce(transformIn, transformOut, attrs, attr, dflt);
}
var enabled = coerce('enabled');
if(!enabled) return transformOut;
/*
* Normally _arrayAttrs is calculated during doCalc, but that comes later.
* Anyway this can change due to *count* aggregations (see below) so it's not
* necessarily the same set.
*
* For performance we turn it into an object of truthy values
* we'll use 1 for arrays we haven't aggregated yet, 0 for finished arrays,
* as distinct from undefined which means this array isn't present in the input
* missing arrays can still be aggregate outputs for *count* aggregations.
*/
var arrayAttrArray = PlotSchema.findArrayAttributes(traceOut);
var arrayAttrs = {};
for(i = 0; i < arrayAttrArray.length; i++) arrayAttrs[arrayAttrArray[i]] = 1;
var groups = coerce('groups');
if(!Array.isArray(groups)) {
if(!arrayAttrs[groups]) {
transformOut.enabled = false;
return transformOut;
}
arrayAttrs[groups] = 0;
}
var aggregationsIn = transformIn.aggregations || [];
var aggregationsOut = transformOut.aggregations = new Array(aggregationsIn.length);
var aggregationOut;
function coercei(attr, dflt) {
return Lib.coerce(aggregationsIn[i], aggregationOut, aggAttrs, attr, dflt);
}
for(i = 0; i < aggregationsIn.length; i++) {
aggregationOut = {_index: i};
var target = coercei('target');
var func = coercei('func');
var enabledi = coercei('enabled');
// add this aggregation to the output only if it's the first instance
// of a valid target attribute - or an unused target attribute with "count"
if(enabledi && target && (arrayAttrs[target] || (func === 'count' && arrayAttrs[target] === undefined))) {
if(func === 'stddev') coercei('funcmode');
arrayAttrs[target] = 0;
aggregationsOut[i] = aggregationOut;
} else aggregationsOut[i] = {enabled: false, _index: i};
}
// any array attributes we haven't yet covered, fill them with the default aggregation
for(i = 0; i < arrayAttrArray.length; i++) {
if(arrayAttrs[arrayAttrArray[i]]) {
aggregationsOut.push({
target: arrayAttrArray[i],
func: aggAttrs.func.dflt,
enabled: true,
_index: -1
});
}
}
return transformOut;
};
exports.calcTransform = function(gd, trace, opts) {
if(!opts.enabled) return;
var groups = opts.groups;
var groupArray = Lib.getTargetArray(trace, {target: groups});
if(!groupArray) return;
var i, vi, groupIndex, newGrouping;
var groupIndices = {};
var indexToPoints = {};
var groupings = [];
var originalPointsAccessor = pointsAccessorFunction(trace.transforms, opts);
var len = groupArray.length;
if(trace._length) len = Math.min(len, trace._length);
for(i = 0; i < len; i++) {
vi = groupArray[i];
groupIndex = groupIndices[vi];
if(groupIndex === undefined) {
groupIndices[vi] = groupings.length;
newGrouping = [i];
groupings.push(newGrouping);
indexToPoints[groupIndices[vi]] = originalPointsAccessor(i);
} else {
groupings[groupIndex].push(i);
indexToPoints[groupIndices[vi]] = (indexToPoints[groupIndices[vi]] || []).concat(originalPointsAccessor(i));
}
}
opts._indexToPoints = indexToPoints;
var aggregations = opts.aggregations;
for(i = 0; i < aggregations.length; i++) {
aggregateOneArray(gd, trace, groupings, aggregations[i]);
}
if(typeof groups === 'string') {
aggregateOneArray(gd, trace, groupings, {
target: groups,
func: 'first',
enabled: true
});
}
trace._length = groupings.length;
};
function aggregateOneArray(gd, trace, groupings, aggregation) {
if(!aggregation.enabled) return;
var attr = aggregation.target;
var targetNP = Lib.nestedProperty(trace, attr);
var arrayIn = targetNP.get();
var conversions = Axes.getDataConversions(gd, trace, attr, arrayIn);
var func = getAggregateFunction(aggregation, conversions);
var arrayOut = new Array(groupings.length);
for(var i = 0; i < groupings.length; i++) {
arrayOut[i] = func(arrayIn, groupings[i]);
}
targetNP.set(arrayOut);
if(aggregation.func === 'count') {
// count does not depend on an input array, so it's likely not part of _arrayAttrs yet
// but after this transform it most definitely *is* an array attribute.
Lib.pushUnique(trace._arrayAttrs, attr);
}
}
function getAggregateFunction(opts, conversions) {
var func = opts.func;
var d2c = conversions.d2c;
var c2d = conversions.c2d;
switch(func) {
// count, first, and last don't depend on anything about the data
// point back to pure functions for performance
case 'count':
return count;
case 'first':
return first;
case 'last':
return last;
case 'sum':
// This will produce output in all cases even though it's nonsensical
// for date or category data.
return function(array, indices) {
var total = 0;
for(var i = 0; i < indices.length; i++) {
var vi = d2c(array[indices[i]]);
if(vi !== BADNUM) total += vi;
}
return c2d(total);
};
case 'avg':
// Generally meaningless for category data but it still does something.
return function(array, indices) {
var total = 0;
var cnt = 0;
for(var i = 0; i < indices.length; i++) {
var vi = d2c(array[indices[i]]);
if(vi !== BADNUM) {
total += vi;
cnt++;
}
}
return cnt ? c2d(total / cnt) : BADNUM;
};
case 'min':
return function(array, indices) {
var out = Infinity;
for(var i = 0; i < indices.length; i++) {
var vi = d2c(array[indices[i]]);
if(vi !== BADNUM) out = Math.min(out, vi);
}
return (out === Infinity) ? BADNUM : c2d(out);
};
case 'max':
return function(array, indices) {
var out = -Infinity;
for(var i = 0; i < indices.length; i++) {
var vi = d2c(array[indices[i]]);
if(vi !== BADNUM) out = Math.max(out, vi);
}
return (out === -Infinity) ? BADNUM : c2d(out);
};
case 'range':
return function(array, indices) {
var min = Infinity;
var max = -Infinity;
for(var i = 0; i < indices.length; i++) {
var vi = d2c(array[indices[i]]);
if(vi !== BADNUM) {
min = Math.min(min, vi);
max = Math.max(max, vi);
}
}
return (max === -Infinity || min === Infinity) ? BADNUM : c2d(max - min);
};
case 'change':
return function(array, indices) {
var first = d2c(array[indices[0]]);
var last = d2c(array[indices[indices.length - 1]]);
return (first === BADNUM || last === BADNUM) ? BADNUM : c2d(last - first);
};
case 'median':
return function(array, indices) {
var sortCalc = [];
for(var i = 0; i < indices.length; i++) {
var vi = d2c(array[indices[i]]);
if(vi !== BADNUM) sortCalc.push(vi);
}
if(!sortCalc.length) return BADNUM;
sortCalc.sort(Lib.sorterAsc);
var mid = (sortCalc.length - 1) / 2;
return c2d((sortCalc[Math.floor(mid)] + sortCalc[Math.ceil(mid)]) / 2);
};
case 'mode':
return function(array, indices) {
var counts = {};
var maxCnt = 0;
var out = BADNUM;
for(var i = 0; i < indices.length; i++) {
var vi = d2c(array[indices[i]]);
if(vi !== BADNUM) {
var counti = counts[vi] = (counts[vi] || 0) + 1;
if(counti > maxCnt) {
maxCnt = counti;
out = vi;
}
}
}
return maxCnt ? c2d(out) : BADNUM;
};
case 'rms':
return function(array, indices) {
var total = 0;
var cnt = 0;
for(var i = 0; i < indices.length; i++) {
var vi = d2c(array[indices[i]]);
if(vi !== BADNUM) {
total += vi * vi;
cnt++;
}
}
return cnt ? c2d(Math.sqrt(total / cnt)) : BADNUM;
};
case 'stddev':
return function(array, indices) {
// balance numerical stability with performance:
// so that we call d2c once per element but don't need to
// store them, reference all to the first element
var total = 0;
var total2 = 0;
var cnt = 1;
var v0 = BADNUM;
var i;
for(i = 0; i < indices.length && v0 === BADNUM; i++) {
v0 = d2c(array[indices[i]]);
}
if(v0 === BADNUM) return BADNUM;
for(; i < indices.length; i++) {
var vi = d2c(array[indices[i]]);
if(vi !== BADNUM) {
var dv = vi - v0;
total += dv;
total2 += dv * dv;
cnt++;
}
}
// This is population std dev, if we want sample std dev
// we would need (...) / (cnt - 1)
// Also note there's no c2d here - that means for dates the result
// is a number of milliseconds, and for categories it's a number
// of category differences, which is not generically meaningful but
// as in other cases we don't forbid it.
var norm = (opts.funcmode === 'sample') ? (cnt - 1) : cnt;
// this is debatable: should a count of 1 return sample stddev of
// 0 or undefined?
if(!norm) return 0;
return Math.sqrt((total2 - (total * total / cnt)) / norm);
};
}
}
function count(array, indices) {
return indices.length;
}
function first(array, indices) {
return array[indices[0]];
}
function last(array, indices) {
return array[indices[indices.length - 1]];
}
},{"../constants/numerical":479,"../lib":503,"../plot_api/plot_schema":542,"../plots/cartesian/axes":554,"./helpers":1116}],1114:[function(_dereq_,module,exports){
'use strict';
var Lib = _dereq_('../lib');
var Registry = _dereq_('../registry');
var Axes = _dereq_('../plots/cartesian/axes');
var pointsAccessorFunction = _dereq_('./helpers').pointsAccessorFunction;
var filterOps = _dereq_('../constants/filter_ops');
var COMPARISON_OPS = filterOps.COMPARISON_OPS;
var INTERVAL_OPS = filterOps.INTERVAL_OPS;
var SET_OPS = filterOps.SET_OPS;
exports.moduleType = 'transform';
exports.name = 'filter';
exports.attributes = {
enabled: {
valType: 'boolean',
dflt: true,
editType: 'calc',
},
target: {
valType: 'string',
strict: true,
noBlank: true,
arrayOk: true,
dflt: 'x',
editType: 'calc',
},
operation: {
valType: 'enumerated',
values: []
.concat(COMPARISON_OPS)
.concat(INTERVAL_OPS)
.concat(SET_OPS),
dflt: '=',
editType: 'calc',
},
value: {
valType: 'any',
dflt: 0,
editType: 'calc',
},
preservegaps: {
valType: 'boolean',
dflt: false,
editType: 'calc',
},
editType: 'calc'
};
exports.supplyDefaults = function(transformIn) {
var transformOut = {};
function coerce(attr, dflt) {
return Lib.coerce(transformIn, transformOut, exports.attributes, attr, dflt);
}
var enabled = coerce('enabled');
if(enabled) {
var target = coerce('target');
if(Lib.isArrayOrTypedArray(target) && target.length === 0) {
transformOut.enabled = false;
return transformOut;
}
coerce('preservegaps');
coerce('operation');
coerce('value');
var handleCalendarDefaults = Registry.getComponentMethod('calendars', 'handleDefaults');
handleCalendarDefaults(transformIn, transformOut, 'valuecalendar', null);
handleCalendarDefaults(transformIn, transformOut, 'targetcalendar', null);
}
return transformOut;
};
exports.calcTransform = function(gd, trace, opts) {
if(!opts.enabled) return;
var targetArray = Lib.getTargetArray(trace, opts);
if(!targetArray) return;
var target = opts.target;
var len = targetArray.length;
if(trace._length) len = Math.min(len, trace._length);
var targetCalendar = opts.targetcalendar;
var arrayAttrs = trace._arrayAttrs;
var preservegaps = opts.preservegaps;
// even if you provide targetcalendar, if target is a string and there
// is a calendar attribute matching target it will get used instead.
if(typeof target === 'string') {
var attrTargetCalendar = Lib.nestedProperty(trace, target + 'calendar').get();
if(attrTargetCalendar) targetCalendar = attrTargetCalendar;
}
var d2c = Axes.getDataToCoordFunc(gd, trace, target, targetArray);
var filterFunc = getFilterFunc(opts, d2c, targetCalendar);
var originalArrays = {};
var indexToPoints = {};
var index = 0;
function forAllAttrs(fn, index) {
for(var j = 0; j < arrayAttrs.length; j++) {
var np = Lib.nestedProperty(trace, arrayAttrs[j]);
fn(np, index);
}
}
var initFn;
var fillFn;
if(preservegaps) {
initFn = function(np) {
originalArrays[np.astr] = Lib.extendDeep([], np.get());
np.set(new Array(len));
};
fillFn = function(np, index) {
var val = originalArrays[np.astr][index];
np.get()[index] = val;
};
} else {
initFn = function(np) {
originalArrays[np.astr] = Lib.extendDeep([], np.get());
np.set([]);
};
fillFn = function(np, index) {
var val = originalArrays[np.astr][index];
np.get().push(val);
};
}
// copy all original array attribute values, and clear arrays in trace
forAllAttrs(initFn);
var originalPointsAccessor = pointsAccessorFunction(trace.transforms, opts);
// loop through filter array, fill trace arrays if passed
for(var i = 0; i < len; i++) {
var passed = filterFunc(targetArray[i]);
if(passed) {
forAllAttrs(fillFn, i);
indexToPoints[index++] = originalPointsAccessor(i);
} else if(preservegaps) index++;
}
opts._indexToPoints = indexToPoints;
trace._length = index;
};
function getFilterFunc(opts, d2c, targetCalendar) {
var operation = opts.operation;
var value = opts.value;
var hasArrayValue = Array.isArray(value);
function isOperationIn(array) {
return array.indexOf(operation) !== -1;
}
var d2cValue = function(v) { return d2c(v, 0, opts.valuecalendar); };
var d2cTarget = function(v) { return d2c(v, 0, targetCalendar); };
var coercedValue;
if(isOperationIn(COMPARISON_OPS)) {
coercedValue = hasArrayValue ? d2cValue(value[0]) : d2cValue(value);
} else if(isOperationIn(INTERVAL_OPS)) {
coercedValue = hasArrayValue ?
[d2cValue(value[0]), d2cValue(value[1])] :
[d2cValue(value), d2cValue(value)];
} else if(isOperationIn(SET_OPS)) {
coercedValue = hasArrayValue ? value.map(d2cValue) : [d2cValue(value)];
}
switch(operation) {
case '=':
return function(v) { return d2cTarget(v) === coercedValue; };
case '!=':
return function(v) { return d2cTarget(v) !== coercedValue; };
case '<':
return function(v) { return d2cTarget(v) < coercedValue; };
case '<=':
return function(v) { return d2cTarget(v) <= coercedValue; };
case '>':
return function(v) { return d2cTarget(v) > coercedValue; };
case '>=':
return function(v) { return d2cTarget(v) >= coercedValue; };
case '[]':
return function(v) {
var cv = d2cTarget(v);
return cv >= coercedValue[0] && cv <= coercedValue[1];
};
case '()':
return function(v) {
var cv = d2cTarget(v);
return cv > coercedValue[0] && cv < coercedValue[1];
};
case '[)':
return function(v) {
var cv = d2cTarget(v);
return cv >= coercedValue[0] && cv < coercedValue[1];
};
case '(]':
return function(v) {
var cv = d2cTarget(v);
return cv > coercedValue[0] && cv <= coercedValue[1];
};
case '][':
return function(v) {
var cv = d2cTarget(v);
return cv <= coercedValue[0] || cv >= coercedValue[1];
};
case ')(':
return function(v) {
var cv = d2cTarget(v);
return cv < coercedValue[0] || cv > coercedValue[1];
};
case '](':
return function(v) {
var cv = d2cTarget(v);
return cv <= coercedValue[0] || cv > coercedValue[1];
};
case ')[':
return function(v) {
var cv = d2cTarget(v);
return cv < coercedValue[0] || cv >= coercedValue[1];
};
case '{}':
return function(v) {
return coercedValue.indexOf(d2cTarget(v)) !== -1;
};
case '}{':
return function(v) {
return coercedValue.indexOf(d2cTarget(v)) === -1;
};
}
}
},{"../constants/filter_ops":475,"../lib":503,"../plots/cartesian/axes":554,"../registry":638,"./helpers":1116}],1115:[function(_dereq_,module,exports){
'use strict';
var Lib = _dereq_('../lib');
var PlotSchema = _dereq_('../plot_api/plot_schema');
var Plots = _dereq_('../plots/plots');
var pointsAccessorFunction = _dereq_('./helpers').pointsAccessorFunction;
exports.moduleType = 'transform';
exports.name = 'groupby';
exports.attributes = {
enabled: {
valType: 'boolean',
dflt: true,
editType: 'calc',
},
groups: {
valType: 'data_array',
dflt: [],
editType: 'calc',
},
nameformat: {
valType: 'string',
editType: 'calc',
},
styles: {
_isLinkedToArray: 'style',
target: {
valType: 'string',
editType: 'calc',
},
value: {
valType: 'any',
dflt: {},
editType: 'calc',
_compareAsJSON: true
},
editType: 'calc'
},
editType: 'calc'
};
/**
* Supply transform attributes defaults
*
* @param {object} transformIn
* object linked to trace.transforms[i] with 'type' set to exports.name
* @param {object} traceOut
* the _fullData trace this transform applies to
* @param {object} layout
* the plot's (not-so-full) layout
* @param {object} traceIn
* the input data trace this transform applies to
*
* @return {object} transformOut
* copy of transformIn that contains attribute defaults
*/
exports.supplyDefaults = function(transformIn, traceOut, layout) {
var i;
var transformOut = {};
function coerce(attr, dflt) {
return Lib.coerce(transformIn, transformOut, exports.attributes, attr, dflt);
}
var enabled = coerce('enabled');
if(!enabled) return transformOut;
coerce('groups');
coerce('nameformat', layout._dataLength > 1 ? '%{group} (%{trace})' : '%{group}');
var styleIn = transformIn.styles;
var styleOut = transformOut.styles = [];
if(styleIn) {
for(i = 0; i < styleIn.length; i++) {
var thisStyle = styleOut[i] = {};
Lib.coerce(styleIn[i], styleOut[i], exports.attributes.styles, 'target');
var value = Lib.coerce(styleIn[i], styleOut[i], exports.attributes.styles, 'value');
// so that you can edit value in place and have Plotly.react notice it, or
// rebuild it every time and have Plotly.react NOT think it changed:
// use _compareAsJSON to say we should diff the _JSON_value
if(Lib.isPlainObject(value)) thisStyle.value = Lib.extendDeep({}, value);
else if(value) delete thisStyle.value;
}
}
return transformOut;
};
/**
* Apply transform !!!
*
* @param {array} data
* array of transformed traces (is [fullTrace] upon first transform)
*
* @param {object} state
* state object which includes:
* - transform {object} full transform attributes
* - fullTrace {object} full trace object which is being transformed
* - fullData {array} full pre-transform(s) data array
* - layout {object} the plot's (not-so-full) layout
*
* @return {object} newData
* array of transformed traces
*/
exports.transform = function(data, state) {
var newTraces, i, j;
var newData = [];
for(i = 0; i < data.length; i++) {
newTraces = transformOne(data[i], state);
for(j = 0; j < newTraces.length; j++) {
newData.push(newTraces[j]);
}
}
return newData;
};
function transformOne(trace, state) {
var i, j, k, attr, srcArray, groupName, newTrace, transforms, arrayLookup;
var groupNameObj;
var opts = state.transform;
var transformIndex = state.transformIndex;
var groups = trace.transforms[transformIndex].groups;
var originalPointsAccessor = pointsAccessorFunction(trace.transforms, opts);
if(!(Lib.isArrayOrTypedArray(groups)) || groups.length === 0) {
return [trace];
}
var groupNames = Lib.filterUnique(groups);
var newData = new Array(groupNames.length);
var len = groups.length;
var arrayAttrs = PlotSchema.findArrayAttributes(trace);
var styles = opts.styles || [];
var styleLookup = {};
for(i = 0; i < styles.length; i++) {
styleLookup[styles[i].target] = styles[i].value;
}
if(opts.styles) {
groupNameObj = Lib.keyedContainer(opts, 'styles', 'target', 'value.name');
}
// An index to map group name --> expanded trace index
var indexLookup = {};
var indexCnts = {};
for(i = 0; i < groupNames.length; i++) {
groupName = groupNames[i];
indexLookup[groupName] = i;
indexCnts[groupName] = 0;
// Start with a deep extend that just copies array references.
newTrace = newData[i] = Lib.extendDeepNoArrays({}, trace);
newTrace._group = groupName;
newTrace.transforms[transformIndex]._indexToPoints = {};
var suppliedName = null;
if(groupNameObj) {
suppliedName = groupNameObj.get(groupName);
}
if(suppliedName || suppliedName === '') {
newTrace.name = suppliedName;
} else {
newTrace.name = Lib.templateString(opts.nameformat, {
trace: trace.name,
group: groupName
});
}
// In order for groups to apply correctly to other transform data (e.g.
// a filter transform), we have to break the connection and clone the
// transforms so that each group writes grouped values into a different
// destination. This function does not break the array reference
// connection between the split transforms it creates. That's handled in
// initialize, which creates a new empty array for each arrayAttr.
transforms = newTrace.transforms;
newTrace.transforms = [];
for(j = 0; j < transforms.length; j++) {
newTrace.transforms[j] = Lib.extendDeepNoArrays({}, transforms[j]);
}
// Initialize empty arrays for the arrayAttrs, to be split in the next step
for(j = 0; j < arrayAttrs.length; j++) {
Lib.nestedProperty(newTrace, arrayAttrs[j]).set([]);
}
}
// For each array attribute including those nested inside this and other
// transforms (small note that we technically only need to do this for
// transforms that have not yet been applied):
for(k = 0; k < arrayAttrs.length; k++) {
attr = arrayAttrs[k];
// Cache all the arrays to which we'll push:
for(j = 0, arrayLookup = []; j < groupNames.length; j++) {
arrayLookup[j] = Lib.nestedProperty(newData[j], attr).get();
}
// Get the input data:
srcArray = Lib.nestedProperty(trace, attr).get();
// Send each data point to the appropriate expanded trace:
for(j = 0; j < len; j++) {
// Map group data --> trace index --> array and push data onto it
arrayLookup[indexLookup[groups[j]]].push(srcArray[j]);
}
}
for(j = 0; j < len; j++) {
newTrace = newData[indexLookup[groups[j]]];
var indexToPoints = newTrace.transforms[transformIndex]._indexToPoints;
indexToPoints[indexCnts[groups[j]]] = originalPointsAccessor(j);
indexCnts[groups[j]]++;
}
for(i = 0; i < groupNames.length; i++) {
groupName = groupNames[i];
newTrace = newData[i];
Plots.clearExpandedTraceDefaultColors(newTrace);
// there's no need to coerce styleLookup[groupName] here
// as another round of supplyDefaults is done on the transformed traces
newTrace = Lib.extendDeepNoArrays(newTrace, styleLookup[groupName] || {});
}
return newData;
}
},{"../lib":503,"../plot_api/plot_schema":542,"../plots/plots":619,"./helpers":1116}],1116:[function(_dereq_,module,exports){
'use strict';
exports.pointsAccessorFunction = function(transforms, opts) {
var tr;
var prevIndexToPoints;
for(var i = 0; i < transforms.length; i++) {
tr = transforms[i];
if(tr === opts) break;
if(!tr._indexToPoints || tr.enabled === false) continue;
prevIndexToPoints = tr._indexToPoints;
}
var originalPointsAccessor = prevIndexToPoints ?
function(i) {return prevIndexToPoints[i];} :
function(i) {return [i];};
return originalPointsAccessor;
};
},{}],1117:[function(_dereq_,module,exports){
'use strict';
var Lib = _dereq_('../lib');
var Axes = _dereq_('../plots/cartesian/axes');
var pointsAccessorFunction = _dereq_('./helpers').pointsAccessorFunction;
var BADNUM = _dereq_('../constants/numerical').BADNUM;
exports.moduleType = 'transform';
exports.name = 'sort';
exports.attributes = {
enabled: {
valType: 'boolean',
dflt: true,
editType: 'calc',
},
target: {
valType: 'string',
strict: true,
noBlank: true,
arrayOk: true,
dflt: 'x',
editType: 'calc',
},
order: {
valType: 'enumerated',
values: ['ascending', 'descending'],
dflt: 'ascending',
editType: 'calc',
},
editType: 'calc'
};
exports.supplyDefaults = function(transformIn) {
var transformOut = {};
function coerce(attr, dflt) {
return Lib.coerce(transformIn, transformOut, exports.attributes, attr, dflt);
}
var enabled = coerce('enabled');
if(enabled) {
coerce('target');
coerce('order');
}
return transformOut;
};
exports.calcTransform = function(gd, trace, opts) {
if(!opts.enabled) return;
var targetArray = Lib.getTargetArray(trace, opts);
if(!targetArray) return;
var target = opts.target;
var len = targetArray.length;
if(trace._length) len = Math.min(len, trace._length);
var arrayAttrs = trace._arrayAttrs;
var d2c = Axes.getDataToCoordFunc(gd, trace, target, targetArray);
var indices = getIndices(opts, targetArray, d2c, len);
var originalPointsAccessor = pointsAccessorFunction(trace.transforms, opts);
var indexToPoints = {};
var i, j;
for(i = 0; i < arrayAttrs.length; i++) {
var np = Lib.nestedProperty(trace, arrayAttrs[i]);
var arrayOld = np.get();
var arrayNew = new Array(len);
for(j = 0; j < len; j++) {
arrayNew[j] = arrayOld[indices[j]];
}
np.set(arrayNew);
}
for(j = 0; j < len; j++) {
indexToPoints[j] = originalPointsAccessor(indices[j]);
}
opts._indexToPoints = indexToPoints;
trace._length = len;
};
function getIndices(opts, targetArray, d2c, len) {
var sortedArray = new Array(len);
var indices = new Array(len);
var i;
for(i = 0; i < len; i++) {
sortedArray[i] = {v: targetArray[i], i: i};
}
sortedArray.sort(getSortFunc(opts, d2c));
for(i = 0; i < len; i++) {
indices[i] = sortedArray[i].i;
}
return indices;
}
function getSortFunc(opts, d2c) {
switch(opts.order) {
case 'ascending':
return function(a, b) {
var ac = d2c(a.v);
var bc = d2c(b.v);
if(ac === BADNUM) {
return 1;
}
if(bc === BADNUM) {
return -1;
}
return ac - bc;
};
case 'descending':
return function(a, b) {
var ac = d2c(a.v);
var bc = d2c(b.v);
if(ac === BADNUM) {
return 1;
}
if(bc === BADNUM) {
return -1;
}
return bc - ac;
};
}
}
},{"../constants/numerical":479,"../lib":503,"../plots/cartesian/axes":554,"./helpers":1116}],1118:[function(_dereq_,module,exports){
'use strict';
// package version injected by `npm run preprocess`
exports.version = '2.6.3';
},{}],1119:[function(_dereq_,module,exports){
(function (global){(function (){
(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.stackgl = f()}})(function(){var define,module,exports;return (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof _dereq_&&_dereq_;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof _dereq_&&_dereq_,i=0;i 0) {
throw new Error('Invalid string. Length must be a multiple of 4')
}
// Trim off extra bytes after placeholder bytes are found
// See: https://github.com/beatgammit/base64-js/issues/42
var validLen = b64.indexOf('=')
if (validLen === -1) validLen = len
var placeHoldersLen = validLen === len
? 0
: 4 - (validLen % 4)
return [validLen, placeHoldersLen]
}
// base64 is 4/3 + up to two characters of the original data
function byteLength (b64) {
var lens = getLens(b64)
var validLen = lens[0]
var placeHoldersLen = lens[1]
return ((validLen + placeHoldersLen) * 3 / 4) - placeHoldersLen
}
function _byteLength (b64, validLen, placeHoldersLen) {
return ((validLen + placeHoldersLen) * 3 / 4) - placeHoldersLen
}
function toByteArray (b64) {
var tmp
var lens = getLens(b64)
var validLen = lens[0]
var placeHoldersLen = lens[1]
var arr = new Arr(_byteLength(b64, validLen, placeHoldersLen))
var curByte = 0
// if there are placeholders, only get up to the last complete 4 chars
var len = placeHoldersLen > 0
? validLen - 4
: validLen
var i
for (i = 0; i < len; i += 4) {
tmp =
(revLookup[b64.charCodeAt(i)] << 18) |
(revLookup[b64.charCodeAt(i + 1)] << 12) |
(revLookup[b64.charCodeAt(i + 2)] << 6) |
revLookup[b64.charCodeAt(i + 3)]
arr[curByte++] = (tmp >> 16) & 0xFF
arr[curByte++] = (tmp >> 8) & 0xFF
arr[curByte++] = tmp & 0xFF
}
if (placeHoldersLen === 2) {
tmp =
(revLookup[b64.charCodeAt(i)] << 2) |
(revLookup[b64.charCodeAt(i + 1)] >> 4)
arr[curByte++] = tmp & 0xFF
}
if (placeHoldersLen === 1) {
tmp =
(revLookup[b64.charCodeAt(i)] << 10) |
(revLookup[b64.charCodeAt(i + 1)] << 4) |
(revLookup[b64.charCodeAt(i + 2)] >> 2)
arr[curByte++] = (tmp >> 8) & 0xFF
arr[curByte++] = tmp & 0xFF
}
return arr
}
function tripletToBase64 (num) {
return lookup[num >> 18 & 0x3F] +
lookup[num >> 12 & 0x3F] +
lookup[num >> 6 & 0x3F] +
lookup[num & 0x3F]
}
function encodeChunk (uint8, start, end) {
var tmp
var output = []
for (var i = start; i < end; i += 3) {
tmp =
((uint8[i] << 16) & 0xFF0000) +
((uint8[i + 1] << 8) & 0xFF00) +
(uint8[i + 2] & 0xFF)
output.push(tripletToBase64(tmp))
}
return output.join('')
}
function fromByteArray (uint8) {
var tmp
var len = uint8.length
var extraBytes = len % 3 // if we have 1 byte left, pad 2 bytes
var parts = []
var maxChunkLength = 16383 // must be multiple of 3
// go through the array every three bytes, we'll deal with trailing stuff later
for (var i = 0, len2 = len - extraBytes; i < len2; i += maxChunkLength) {
parts.push(encodeChunk(
uint8, i, (i + maxChunkLength) > len2 ? len2 : (i + maxChunkLength)
))
}
// pad the end with zeros, but make sure to not forget the extra bytes
if (extraBytes === 1) {
tmp = uint8[len - 1]
parts.push(
lookup[tmp >> 2] +
lookup[(tmp << 4) & 0x3F] +
'=='
)
} else if (extraBytes === 2) {
tmp = (uint8[len - 2] << 8) + uint8[len - 1]
parts.push(
lookup[tmp >> 10] +
lookup[(tmp >> 4) & 0x3F] +
lookup[(tmp << 2) & 0x3F] +
'='
)
}
return parts.join('')
}
},{}],2:[function(_glvis_,module,exports){
},{}],3:[function(_glvis_,module,exports){
(function (Buffer){(function (){
/*!
* The buffer module from node.js, for the browser.
*
* @author Feross Aboukhadijeh
* @license MIT
*/
/* eslint-disable no-proto */
'use strict'
var base64 = _glvis_('base64-js')
var ieee754 = _glvis_('ieee754')
exports.Buffer = Buffer
exports.SlowBuffer = SlowBuffer
exports.INSPECT_MAX_BYTES = 50
var K_MAX_LENGTH = 0x7fffffff
exports.kMaxLength = K_MAX_LENGTH
/**
* If `Buffer.TYPED_ARRAY_SUPPORT`:
* === true Use Uint8Array implementation (fastest)
* === false Print warning and recommend using `buffer` v4.x which has an Object
* implementation (most compatible, even IE6)
*
* Browsers that support typed arrays are IE 10+, Firefox 4+, Chrome 7+, Safari 5.1+,
* Opera 11.6+, iOS 4.2+.
*
* We report that the browser does not support typed arrays if the are not subclassable
* using __proto__. Firefox 4-29 lacks support for adding new properties to `Uint8Array`
* (See: https://bugzilla.mozilla.org/show_bug.cgi?id=695438). IE 10 lacks support
* for __proto__ and has a buggy typed array implementation.
*/
Buffer.TYPED_ARRAY_SUPPORT = typedArraySupport()
if (!Buffer.TYPED_ARRAY_SUPPORT && typeof console !== 'undefined' &&
typeof console.error === 'function') {
console.error(
'This browser lacks typed array (Uint8Array) support which is required by ' +
'`buffer` v5.x. Use `buffer` v4.x if you require old browser support.'
)
}
function typedArraySupport () {
// Can typed array instances can be augmented?
try {
var arr = new Uint8Array(1)
arr.__proto__ = { __proto__: Uint8Array.prototype, foo: function () { return 42 } }
return arr.foo() === 42
} catch (e) {
return false
}
}
Object.defineProperty(Buffer.prototype, 'parent', {
enumerable: true,
get: function () {
if (!Buffer.isBuffer(this)) return undefined
return this.buffer
}
})
Object.defineProperty(Buffer.prototype, 'offset', {
enumerable: true,
get: function () {
if (!Buffer.isBuffer(this)) return undefined
return this.byteOffset
}
})
function createBuffer (length) {
if (length > K_MAX_LENGTH) {
throw new RangeError('The value "' + length + '" is invalid for option "size"')
}
// Return an augmented `Uint8Array` instance
var buf = new Uint8Array(length)
buf.__proto__ = Buffer.prototype
return buf
}
/**
* The Buffer constructor returns instances of `Uint8Array` that have their
* prototype changed to `Buffer.prototype`. Furthermore, `Buffer` is a subclass of
* `Uint8Array`, so the returned instances will have all the node `Buffer` methods
* and the `Uint8Array` methods. Square bracket notation works as expected -- it
* returns a single octet.
*
* The `Uint8Array` prototype remains unmodified.
*/
function Buffer (arg, encodingOrOffset, length) {
// Common case.
if (typeof arg === 'number') {
if (typeof encodingOrOffset === 'string') {
throw new TypeError(
'The "string" argument must be of type string. Received type number'
)
}
return allocUnsafe(arg)
}
return from(arg, encodingOrOffset, length)
}
// Fix subarray() in ES2016. See: https://github.com/feross/buffer/pull/97
if (typeof Symbol !== 'undefined' && Symbol.species != null &&
Buffer[Symbol.species] === Buffer) {
Object.defineProperty(Buffer, Symbol.species, {
value: null,
configurable: true,
enumerable: false,
writable: false
})
}
Buffer.poolSize = 8192 // not used by this implementation
function from (value, encodingOrOffset, length) {
if (typeof value === 'string') {
return fromString(value, encodingOrOffset)
}
if (ArrayBuffer.isView(value)) {
return fromArrayLike(value)
}
if (value == null) {
throw TypeError(
'The first argument must be one of type string, Buffer, ArrayBuffer, Array, ' +
'or Array-like Object. Received type ' + (typeof value)
)
}
if (isInstance(value, ArrayBuffer) ||
(value && isInstance(value.buffer, ArrayBuffer))) {
return fromArrayBuffer(value, encodingOrOffset, length)
}
if (typeof value === 'number') {
throw new TypeError(
'The "value" argument must not be of type number. Received type number'
)
}
var valueOf = value.valueOf && value.valueOf()
if (valueOf != null && valueOf !== value) {
return Buffer.from(valueOf, encodingOrOffset, length)
}
var b = fromObject(value)
if (b) return b
if (typeof Symbol !== 'undefined' && Symbol.toPrimitive != null &&
typeof value[Symbol.toPrimitive] === 'function') {
return Buffer.from(
value[Symbol.toPrimitive]('string'), encodingOrOffset, length
)
}
throw new TypeError(
'The first argument must be one of type string, Buffer, ArrayBuffer, Array, ' +
'or Array-like Object. Received type ' + (typeof value)
)
}
/**
* Functionally equivalent to Buffer(arg, encoding) but throws a TypeError
* if value is a number.
* Buffer.from(str[, encoding])
* Buffer.from(array)
* Buffer.from(buffer)
* Buffer.from(arrayBuffer[, byteOffset[, length]])
**/
Buffer.from = function (value, encodingOrOffset, length) {
return from(value, encodingOrOffset, length)
}
// Note: Change prototype *after* Buffer.from is defined to workaround Chrome bug:
// https://github.com/feross/buffer/pull/148
Buffer.prototype.__proto__ = Uint8Array.prototype
Buffer.__proto__ = Uint8Array
function assertSize (size) {
if (typeof size !== 'number') {
throw new TypeError('"size" argument must be of type number')
} else if (size < 0) {
throw new RangeError('The value "' + size + '" is invalid for option "size"')
}
}
function alloc (size, fill, encoding) {
assertSize(size)
if (size <= 0) {
return createBuffer(size)
}
if (fill !== undefined) {
// Only pay attention to encoding if it's a string. This
// prevents accidentally sending in a number that would
// be interpretted as a start offset.
return typeof encoding === 'string'
? createBuffer(size).fill(fill, encoding)
: createBuffer(size).fill(fill)
}
return createBuffer(size)
}
/**
* Creates a new filled Buffer instance.
* alloc(size[, fill[, encoding]])
**/
Buffer.alloc = function (size, fill, encoding) {
return alloc(size, fill, encoding)
}
function allocUnsafe (size) {
assertSize(size)
return createBuffer(size < 0 ? 0 : checked(size) | 0)
}
/**
* Equivalent to Buffer(num), by default creates a non-zero-filled Buffer instance.
* */
Buffer.allocUnsafe = function (size) {
return allocUnsafe(size)
}
/**
* Equivalent to SlowBuffer(num), by default creates a non-zero-filled Buffer instance.
*/
Buffer.allocUnsafeSlow = function (size) {
return allocUnsafe(size)
}
function fromString (string, encoding) {
if (typeof encoding !== 'string' || encoding === '') {
encoding = 'utf8'
}
if (!Buffer.isEncoding(encoding)) {
throw new TypeError('Unknown encoding: ' + encoding)
}
var length = byteLength(string, encoding) | 0
var buf = createBuffer(length)
var actual = buf.write(string, encoding)
if (actual !== length) {
// Writing a hex string, for example, that contains invalid characters will
// cause everything after the first invalid character to be ignored. (e.g.
// 'abxxcd' will be treated as 'ab')
buf = buf.slice(0, actual)
}
return buf
}
function fromArrayLike (array) {
var length = array.length < 0 ? 0 : checked(array.length) | 0
var buf = createBuffer(length)
for (var i = 0; i < length; i += 1) {
buf[i] = array[i] & 255
}
return buf
}
function fromArrayBuffer (array, byteOffset, length) {
if (byteOffset < 0 || array.byteLength < byteOffset) {
throw new RangeError('"offset" is outside of buffer bounds')
}
if (array.byteLength < byteOffset + (length || 0)) {
throw new RangeError('"length" is outside of buffer bounds')
}
var buf
if (byteOffset === undefined && length === undefined) {
buf = new Uint8Array(array)
} else if (length === undefined) {
buf = new Uint8Array(array, byteOffset)
} else {
buf = new Uint8Array(array, byteOffset, length)
}
// Return an augmented `Uint8Array` instance
buf.__proto__ = Buffer.prototype
return buf
}
function fromObject (obj) {
if (Buffer.isBuffer(obj)) {
var len = checked(obj.length) | 0
var buf = createBuffer(len)
if (buf.length === 0) {
return buf
}
obj.copy(buf, 0, 0, len)
return buf
}
if (obj.length !== undefined) {
if (typeof obj.length !== 'number' || numberIsNaN(obj.length)) {
return createBuffer(0)
}
return fromArrayLike(obj)
}
if (obj.type === 'Buffer' && Array.isArray(obj.data)) {
return fromArrayLike(obj.data)
}
}
function checked (length) {
// Note: cannot use `length < K_MAX_LENGTH` here because that fails when
// length is NaN (which is otherwise coerced to zero.)
if (length >= K_MAX_LENGTH) {
throw new RangeError('Attempt to allocate Buffer larger than maximum ' +
'size: 0x' + K_MAX_LENGTH.toString(16) + ' bytes')
}
return length | 0
}
function SlowBuffer (length) {
if (+length != length) { // eslint-disable-line eqeqeq
length = 0
}
return Buffer.alloc(+length)
}
Buffer.isBuffer = function isBuffer (b) {
return b != null && b._isBuffer === true &&
b !== Buffer.prototype // so Buffer.isBuffer(Buffer.prototype) will be false
}
Buffer.compare = function compare (a, b) {
if (isInstance(a, Uint8Array)) a = Buffer.from(a, a.offset, a.byteLength)
if (isInstance(b, Uint8Array)) b = Buffer.from(b, b.offset, b.byteLength)
if (!Buffer.isBuffer(a) || !Buffer.isBuffer(b)) {
throw new TypeError(
'The "buf1", "buf2" arguments must be one of type Buffer or Uint8Array'
)
}
if (a === b) return 0
var x = a.length
var y = b.length
for (var i = 0, len = Math.min(x, y); i < len; ++i) {
if (a[i] !== b[i]) {
x = a[i]
y = b[i]
break
}
}
if (x < y) return -1
if (y < x) return 1
return 0
}
Buffer.isEncoding = function isEncoding (encoding) {
switch (String(encoding).toLowerCase()) {
case 'hex':
case 'utf8':
case 'utf-8':
case 'ascii':
case 'latin1':
case 'binary':
case 'base64':
case 'ucs2':
case 'ucs-2':
case 'utf16le':
case 'utf-16le':
return true
default:
return false
}
}
Buffer.concat = function concat (list, length) {
if (!Array.isArray(list)) {
throw new TypeError('"list" argument must be an Array of Buffers')
}
if (list.length === 0) {
return Buffer.alloc(0)
}
var i
if (length === undefined) {
length = 0
for (i = 0; i < list.length; ++i) {
length += list[i].length
}
}
var buffer = Buffer.allocUnsafe(length)
var pos = 0
for (i = 0; i < list.length; ++i) {
var buf = list[i]
if (isInstance(buf, Uint8Array)) {
buf = Buffer.from(buf)
}
if (!Buffer.isBuffer(buf)) {
throw new TypeError('"list" argument must be an Array of Buffers')
}
buf.copy(buffer, pos)
pos += buf.length
}
return buffer
}
function byteLength (string, encoding) {
if (Buffer.isBuffer(string)) {
return string.length
}
if (ArrayBuffer.isView(string) || isInstance(string, ArrayBuffer)) {
return string.byteLength
}
if (typeof string !== 'string') {
throw new TypeError(
'The "string" argument must be one of type string, Buffer, or ArrayBuffer. ' +
'Received type ' + typeof string
)
}
var len = string.length
var mustMatch = (arguments.length > 2 && arguments[2] === true)
if (!mustMatch && len === 0) return 0
// Use a for loop to avoid recursion
var loweredCase = false
for (;;) {
switch (encoding) {
case 'ascii':
case 'latin1':
case 'binary':
return len
case 'utf8':
case 'utf-8':
return utf8ToBytes(string).length
case 'ucs2':
case 'ucs-2':
case 'utf16le':
case 'utf-16le':
return len * 2
case 'hex':
return len >>> 1
case 'base64':
return base64ToBytes(string).length
default:
if (loweredCase) {
return mustMatch ? -1 : utf8ToBytes(string).length // assume utf8
}
encoding = ('' + encoding).toLowerCase()
loweredCase = true
}
}
}
Buffer.byteLength = byteLength
function slowToString (encoding, start, end) {
var loweredCase = false
// No need to verify that "this.length <= MAX_UINT32" since it's a read-only
// property of a typed array.
// This behaves neither like String nor Uint8Array in that we set start/end
// to their upper/lower bounds if the value passed is out of range.
// undefined is handled specially as per ECMA-262 6th Edition,
// Section 13.3.3.7 Runtime Semantics: KeyedBindingInitialization.
if (start === undefined || start < 0) {
start = 0
}
// Return early if start > this.length. Done here to prevent potential uint32
// coercion fail below.
if (start > this.length) {
return ''
}
if (end === undefined || end > this.length) {
end = this.length
}
if (end <= 0) {
return ''
}
// Force coersion to uint32. This will also coerce falsey/NaN values to 0.
end >>>= 0
start >>>= 0
if (end <= start) {
return ''
}
if (!encoding) encoding = 'utf8'
while (true) {
switch (encoding) {
case 'hex':
return hexSlice(this, start, end)
case 'utf8':
case 'utf-8':
return utf8Slice(this, start, end)
case 'ascii':
return asciiSlice(this, start, end)
case 'latin1':
case 'binary':
return latin1Slice(this, start, end)
case 'base64':
return base64Slice(this, start, end)
case 'ucs2':
case 'ucs-2':
case 'utf16le':
case 'utf-16le':
return utf16leSlice(this, start, end)
default:
if (loweredCase) throw new TypeError('Unknown encoding: ' + encoding)
encoding = (encoding + '').toLowerCase()
loweredCase = true
}
}
}
// This property is used by `Buffer.isBuffer` (and the `is-buffer` npm package)
// to detect a Buffer instance. It's not possible to use `instanceof Buffer`
// reliably in a browserify context because there could be multiple different
// copies of the 'buffer' package in use. This method works even for Buffer
// instances that were created from another copy of the `buffer` package.
// See: https://github.com/feross/buffer/issues/154
Buffer.prototype._isBuffer = true
function swap (b, n, m) {
var i = b[n]
b[n] = b[m]
b[m] = i
}
Buffer.prototype.swap16 = function swap16 () {
var len = this.length
if (len % 2 !== 0) {
throw new RangeError('Buffer size must be a multiple of 16-bits')
}
for (var i = 0; i < len; i += 2) {
swap(this, i, i + 1)
}
return this
}
Buffer.prototype.swap32 = function swap32 () {
var len = this.length
if (len % 4 !== 0) {
throw new RangeError('Buffer size must be a multiple of 32-bits')
}
for (var i = 0; i < len; i += 4) {
swap(this, i, i + 3)
swap(this, i + 1, i + 2)
}
return this
}
Buffer.prototype.swap64 = function swap64 () {
var len = this.length
if (len % 8 !== 0) {
throw new RangeError('Buffer size must be a multiple of 64-bits')
}
for (var i = 0; i < len; i += 8) {
swap(this, i, i + 7)
swap(this, i + 1, i + 6)
swap(this, i + 2, i + 5)
swap(this, i + 3, i + 4)
}
return this
}
Buffer.prototype.toString = function toString () {
var length = this.length
if (length === 0) return ''
if (arguments.length === 0) return utf8Slice(this, 0, length)
return slowToString.apply(this, arguments)
}
Buffer.prototype.toLocaleString = Buffer.prototype.toString
Buffer.prototype.equals = function equals (b) {
if (!Buffer.isBuffer(b)) throw new TypeError('Argument must be a Buffer')
if (this === b) return true
return Buffer.compare(this, b) === 0
}
Buffer.prototype.inspect = function inspect () {
var str = ''
var max = exports.INSPECT_MAX_BYTES
str = this.toString('hex', 0, max).replace(/(.{2})/g, '$1 ').trim()
if (this.length > max) str += ' ... '
return ''
}
Buffer.prototype.compare = function compare (target, start, end, thisStart, thisEnd) {
if (isInstance(target, Uint8Array)) {
target = Buffer.from(target, target.offset, target.byteLength)
}
if (!Buffer.isBuffer(target)) {
throw new TypeError(
'The "target" argument must be one of type Buffer or Uint8Array. ' +
'Received type ' + (typeof target)
)
}
if (start === undefined) {
start = 0
}
if (end === undefined) {
end = target ? target.length : 0
}
if (thisStart === undefined) {
thisStart = 0
}
if (thisEnd === undefined) {
thisEnd = this.length
}
if (start < 0 || end > target.length || thisStart < 0 || thisEnd > this.length) {
throw new RangeError('out of range index')
}
if (thisStart >= thisEnd && start >= end) {
return 0
}
if (thisStart >= thisEnd) {
return -1
}
if (start >= end) {
return 1
}
start >>>= 0
end >>>= 0
thisStart >>>= 0
thisEnd >>>= 0
if (this === target) return 0
var x = thisEnd - thisStart
var y = end - start
var len = Math.min(x, y)
var thisCopy = this.slice(thisStart, thisEnd)
var targetCopy = target.slice(start, end)
for (var i = 0; i < len; ++i) {
if (thisCopy[i] !== targetCopy[i]) {
x = thisCopy[i]
y = targetCopy[i]
break
}
}
if (x < y) return -1
if (y < x) return 1
return 0
}
// Finds either the first index of `val` in `buffer` at offset >= `byteOffset`,
// OR the last index of `val` in `buffer` at offset <= `byteOffset`.
//
// Arguments:
// - buffer - a Buffer to search
// - val - a string, Buffer, or number
// - byteOffset - an index into `buffer`; will be clamped to an int32
// - encoding - an optional encoding, relevant is val is a string
// - dir - true for indexOf, false for lastIndexOf
function bidirectionalIndexOf (buffer, val, byteOffset, encoding, dir) {
// Empty buffer means no match
if (buffer.length === 0) return -1
// Normalize byteOffset
if (typeof byteOffset === 'string') {
encoding = byteOffset
byteOffset = 0
} else if (byteOffset > 0x7fffffff) {
byteOffset = 0x7fffffff
} else if (byteOffset < -0x80000000) {
byteOffset = -0x80000000
}
byteOffset = +byteOffset // Coerce to Number.
if (numberIsNaN(byteOffset)) {
// byteOffset: it it's undefined, null, NaN, "foo", etc, search whole buffer
byteOffset = dir ? 0 : (buffer.length - 1)
}
// Normalize byteOffset: negative offsets start from the end of the buffer
if (byteOffset < 0) byteOffset = buffer.length + byteOffset
if (byteOffset >= buffer.length) {
if (dir) return -1
else byteOffset = buffer.length - 1
} else if (byteOffset < 0) {
if (dir) byteOffset = 0
else return -1
}
// Normalize val
if (typeof val === 'string') {
val = Buffer.from(val, encoding)
}
// Finally, search either indexOf (if dir is true) or lastIndexOf
if (Buffer.isBuffer(val)) {
// Special case: looking for empty string/buffer always fails
if (val.length === 0) {
return -1
}
return arrayIndexOf(buffer, val, byteOffset, encoding, dir)
} else if (typeof val === 'number') {
val = val & 0xFF // Search for a byte value [0-255]
if (typeof Uint8Array.prototype.indexOf === 'function') {
if (dir) {
return Uint8Array.prototype.indexOf.call(buffer, val, byteOffset)
} else {
return Uint8Array.prototype.lastIndexOf.call(buffer, val, byteOffset)
}
}
return arrayIndexOf(buffer, [ val ], byteOffset, encoding, dir)
}
throw new TypeError('val must be string, number or Buffer')
}
function arrayIndexOf (arr, val, byteOffset, encoding, dir) {
var indexSize = 1
var arrLength = arr.length
var valLength = val.length
if (encoding !== undefined) {
encoding = String(encoding).toLowerCase()
if (encoding === 'ucs2' || encoding === 'ucs-2' ||
encoding === 'utf16le' || encoding === 'utf-16le') {
if (arr.length < 2 || val.length < 2) {
return -1
}
indexSize = 2
arrLength /= 2
valLength /= 2
byteOffset /= 2
}
}
function read (buf, i) {
if (indexSize === 1) {
return buf[i]
} else {
return buf.readUInt16BE(i * indexSize)
}
}
var i
if (dir) {
var foundIndex = -1
for (i = byteOffset; i < arrLength; i++) {
if (read(arr, i) === read(val, foundIndex === -1 ? 0 : i - foundIndex)) {
if (foundIndex === -1) foundIndex = i
if (i - foundIndex + 1 === valLength) return foundIndex * indexSize
} else {
if (foundIndex !== -1) i -= i - foundIndex
foundIndex = -1
}
}
} else {
if (byteOffset + valLength > arrLength) byteOffset = arrLength - valLength
for (i = byteOffset; i >= 0; i--) {
var found = true
for (var j = 0; j < valLength; j++) {
if (read(arr, i + j) !== read(val, j)) {
found = false
break
}
}
if (found) return i
}
}
return -1
}
Buffer.prototype.includes = function includes (val, byteOffset, encoding) {
return this.indexOf(val, byteOffset, encoding) !== -1
}
Buffer.prototype.indexOf = function indexOf (val, byteOffset, encoding) {
return bidirectionalIndexOf(this, val, byteOffset, encoding, true)
}
Buffer.prototype.lastIndexOf = function lastIndexOf (val, byteOffset, encoding) {
return bidirectionalIndexOf(this, val, byteOffset, encoding, false)
}
function hexWrite (buf, string, offset, length) {
offset = Number(offset) || 0
var remaining = buf.length - offset
if (!length) {
length = remaining
} else {
length = Number(length)
if (length > remaining) {
length = remaining
}
}
var strLen = string.length
if (length > strLen / 2) {
length = strLen / 2
}
for (var i = 0; i < length; ++i) {
var parsed = parseInt(string.substr(i * 2, 2), 16)
if (numberIsNaN(parsed)) return i
buf[offset + i] = parsed
}
return i
}
function utf8Write (buf, string, offset, length) {
return blitBuffer(utf8ToBytes(string, buf.length - offset), buf, offset, length)
}
function asciiWrite (buf, string, offset, length) {
return blitBuffer(asciiToBytes(string), buf, offset, length)
}
function latin1Write (buf, string, offset, length) {
return asciiWrite(buf, string, offset, length)
}
function base64Write (buf, string, offset, length) {
return blitBuffer(base64ToBytes(string), buf, offset, length)
}
function ucs2Write (buf, string, offset, length) {
return blitBuffer(utf16leToBytes(string, buf.length - offset), buf, offset, length)
}
Buffer.prototype.write = function write (string, offset, length, encoding) {
// Buffer#write(string)
if (offset === undefined) {
encoding = 'utf8'
length = this.length
offset = 0
// Buffer#write(string, encoding)
} else if (length === undefined && typeof offset === 'string') {
encoding = offset
length = this.length
offset = 0
// Buffer#write(string, offset[, length][, encoding])
} else if (isFinite(offset)) {
offset = offset >>> 0
if (isFinite(length)) {
length = length >>> 0
if (encoding === undefined) encoding = 'utf8'
} else {
encoding = length
length = undefined
}
} else {
throw new Error(
'Buffer.write(string, encoding, offset[, length]) is no longer supported'
)
}
var remaining = this.length - offset
if (length === undefined || length > remaining) length = remaining
if ((string.length > 0 && (length < 0 || offset < 0)) || offset > this.length) {
throw new RangeError('Attempt to write outside buffer bounds')
}
if (!encoding) encoding = 'utf8'
var loweredCase = false
for (;;) {
switch (encoding) {
case 'hex':
return hexWrite(this, string, offset, length)
case 'utf8':
case 'utf-8':
return utf8Write(this, string, offset, length)
case 'ascii':
return asciiWrite(this, string, offset, length)
case 'latin1':
case 'binary':
return latin1Write(this, string, offset, length)
case 'base64':
// Warning: maxLength not taken into account in base64Write
return base64Write(this, string, offset, length)
case 'ucs2':
case 'ucs-2':
case 'utf16le':
case 'utf-16le':
return ucs2Write(this, string, offset, length)
default:
if (loweredCase) throw new TypeError('Unknown encoding: ' + encoding)
encoding = ('' + encoding).toLowerCase()
loweredCase = true
}
}
}
Buffer.prototype.toJSON = function toJSON () {
return {
type: 'Buffer',
data: Array.prototype.slice.call(this._arr || this, 0)
}
}
function base64Slice (buf, start, end) {
if (start === 0 && end === buf.length) {
return base64.fromByteArray(buf)
} else {
return base64.fromByteArray(buf.slice(start, end))
}
}
function utf8Slice (buf, start, end) {
end = Math.min(buf.length, end)
var res = []
var i = start
while (i < end) {
var firstByte = buf[i]
var codePoint = null
var bytesPerSequence = (firstByte > 0xEF) ? 4
: (firstByte > 0xDF) ? 3
: (firstByte > 0xBF) ? 2
: 1
if (i + bytesPerSequence <= end) {
var secondByte, thirdByte, fourthByte, tempCodePoint
switch (bytesPerSequence) {
case 1:
if (firstByte < 0x80) {
codePoint = firstByte
}
break
case 2:
secondByte = buf[i + 1]
if ((secondByte & 0xC0) === 0x80) {
tempCodePoint = (firstByte & 0x1F) << 0x6 | (secondByte & 0x3F)
if (tempCodePoint > 0x7F) {
codePoint = tempCodePoint
}
}
break
case 3:
secondByte = buf[i + 1]
thirdByte = buf[i + 2]
if ((secondByte & 0xC0) === 0x80 && (thirdByte & 0xC0) === 0x80) {
tempCodePoint = (firstByte & 0xF) << 0xC | (secondByte & 0x3F) << 0x6 | (thirdByte & 0x3F)
if (tempCodePoint > 0x7FF && (tempCodePoint < 0xD800 || tempCodePoint > 0xDFFF)) {
codePoint = tempCodePoint
}
}
break
case 4:
secondByte = buf[i + 1]
thirdByte = buf[i + 2]
fourthByte = buf[i + 3]
if ((secondByte & 0xC0) === 0x80 && (thirdByte & 0xC0) === 0x80 && (fourthByte & 0xC0) === 0x80) {
tempCodePoint = (firstByte & 0xF) << 0x12 | (secondByte & 0x3F) << 0xC | (thirdByte & 0x3F) << 0x6 | (fourthByte & 0x3F)
if (tempCodePoint > 0xFFFF && tempCodePoint < 0x110000) {
codePoint = tempCodePoint
}
}
}
}
if (codePoint === null) {
// we did not generate a valid codePoint so insert a
// replacement char (U+FFFD) and advance only 1 byte
codePoint = 0xFFFD
bytesPerSequence = 1
} else if (codePoint > 0xFFFF) {
// encode to utf16 (surrogate pair dance)
codePoint -= 0x10000
res.push(codePoint >>> 10 & 0x3FF | 0xD800)
codePoint = 0xDC00 | codePoint & 0x3FF
}
res.push(codePoint)
i += bytesPerSequence
}
return decodeCodePointsArray(res)
}
// Based on http://stackoverflow.com/a/22747272/680742, the browser with
// the lowest limit is Chrome, with 0x10000 args.
// We go 1 magnitude less, for safety
var MAX_ARGUMENTS_LENGTH = 0x1000
function decodeCodePointsArray (codePoints) {
var len = codePoints.length
if (len <= MAX_ARGUMENTS_LENGTH) {
return String.fromCharCode.apply(String, codePoints) // avoid extra slice()
}
// Decode in chunks to avoid "call stack size exceeded".
var res = ''
var i = 0
while (i < len) {
res += String.fromCharCode.apply(
String,
codePoints.slice(i, i += MAX_ARGUMENTS_LENGTH)
)
}
return res
}
function asciiSlice (buf, start, end) {
var ret = ''
end = Math.min(buf.length, end)
for (var i = start; i < end; ++i) {
ret += String.fromCharCode(buf[i] & 0x7F)
}
return ret
}
function latin1Slice (buf, start, end) {
var ret = ''
end = Math.min(buf.length, end)
for (var i = start; i < end; ++i) {
ret += String.fromCharCode(buf[i])
}
return ret
}
function hexSlice (buf, start, end) {
var len = buf.length
if (!start || start < 0) start = 0
if (!end || end < 0 || end > len) end = len
var out = ''
for (var i = start; i < end; ++i) {
out += toHex(buf[i])
}
return out
}
function utf16leSlice (buf, start, end) {
var bytes = buf.slice(start, end)
var res = ''
for (var i = 0; i < bytes.length; i += 2) {
res += String.fromCharCode(bytes[i] + (bytes[i + 1] * 256))
}
return res
}
Buffer.prototype.slice = function slice (start, end) {
var len = this.length
start = ~~start
end = end === undefined ? len : ~~end
if (start < 0) {
start += len
if (start < 0) start = 0
} else if (start > len) {
start = len
}
if (end < 0) {
end += len
if (end < 0) end = 0
} else if (end > len) {
end = len
}
if (end < start) end = start
var newBuf = this.subarray(start, end)
// Return an augmented `Uint8Array` instance
newBuf.__proto__ = Buffer.prototype
return newBuf
}
/*
* Need to make sure that buffer isn't trying to write out of bounds.
*/
function checkOffset (offset, ext, length) {
if ((offset % 1) !== 0 || offset < 0) throw new RangeError('offset is not uint')
if (offset + ext > length) throw new RangeError('Trying to access beyond buffer length')
}
Buffer.prototype.readUIntLE = function readUIntLE (offset, byteLength, noAssert) {
offset = offset >>> 0
byteLength = byteLength >>> 0
if (!noAssert) checkOffset(offset, byteLength, this.length)
var val = this[offset]
var mul = 1
var i = 0
while (++i < byteLength && (mul *= 0x100)) {
val += this[offset + i] * mul
}
return val
}
Buffer.prototype.readUIntBE = function readUIntBE (offset, byteLength, noAssert) {
offset = offset >>> 0
byteLength = byteLength >>> 0
if (!noAssert) {
checkOffset(offset, byteLength, this.length)
}
var val = this[offset + --byteLength]
var mul = 1
while (byteLength > 0 && (mul *= 0x100)) {
val += this[offset + --byteLength] * mul
}
return val
}
Buffer.prototype.readUInt8 = function readUInt8 (offset, noAssert) {
offset = offset >>> 0
if (!noAssert) checkOffset(offset, 1, this.length)
return this[offset]
}
Buffer.prototype.readUInt16LE = function readUInt16LE (offset, noAssert) {
offset = offset >>> 0
if (!noAssert) checkOffset(offset, 2, this.length)
return this[offset] | (this[offset + 1] << 8)
}
Buffer.prototype.readUInt16BE = function readUInt16BE (offset, noAssert) {
offset = offset >>> 0
if (!noAssert) checkOffset(offset, 2, this.length)
return (this[offset] << 8) | this[offset + 1]
}
Buffer.prototype.readUInt32LE = function readUInt32LE (offset, noAssert) {
offset = offset >>> 0
if (!noAssert) checkOffset(offset, 4, this.length)
return ((this[offset]) |
(this[offset + 1] << 8) |
(this[offset + 2] << 16)) +
(this[offset + 3] * 0x1000000)
}
Buffer.prototype.readUInt32BE = function readUInt32BE (offset, noAssert) {
offset = offset >>> 0
if (!noAssert) checkOffset(offset, 4, this.length)
return (this[offset] * 0x1000000) +
((this[offset + 1] << 16) |
(this[offset + 2] << 8) |
this[offset + 3])
}
Buffer.prototype.readIntLE = function readIntLE (offset, byteLength, noAssert) {
offset = offset >>> 0
byteLength = byteLength >>> 0
if (!noAssert) checkOffset(offset, byteLength, this.length)
var val = this[offset]
var mul = 1
var i = 0
while (++i < byteLength && (mul *= 0x100)) {
val += this[offset + i] * mul
}
mul *= 0x80
if (val >= mul) val -= Math.pow(2, 8 * byteLength)
return val
}
Buffer.prototype.readIntBE = function readIntBE (offset, byteLength, noAssert) {
offset = offset >>> 0
byteLength = byteLength >>> 0
if (!noAssert) checkOffset(offset, byteLength, this.length)
var i = byteLength
var mul = 1
var val = this[offset + --i]
while (i > 0 && (mul *= 0x100)) {
val += this[offset + --i] * mul
}
mul *= 0x80
if (val >= mul) val -= Math.pow(2, 8 * byteLength)
return val
}
Buffer.prototype.readInt8 = function readInt8 (offset, noAssert) {
offset = offset >>> 0
if (!noAssert) checkOffset(offset, 1, this.length)
if (!(this[offset] & 0x80)) return (this[offset])
return ((0xff - this[offset] + 1) * -1)
}
Buffer.prototype.readInt16LE = function readInt16LE (offset, noAssert) {
offset = offset >>> 0
if (!noAssert) checkOffset(offset, 2, this.length)
var val = this[offset] | (this[offset + 1] << 8)
return (val & 0x8000) ? val | 0xFFFF0000 : val
}
Buffer.prototype.readInt16BE = function readInt16BE (offset, noAssert) {
offset = offset >>> 0
if (!noAssert) checkOffset(offset, 2, this.length)
var val = this[offset + 1] | (this[offset] << 8)
return (val & 0x8000) ? val | 0xFFFF0000 : val
}
Buffer.prototype.readInt32LE = function readInt32LE (offset, noAssert) {
offset = offset >>> 0
if (!noAssert) checkOffset(offset, 4, this.length)
return (this[offset]) |
(this[offset + 1] << 8) |
(this[offset + 2] << 16) |
(this[offset + 3] << 24)
}
Buffer.prototype.readInt32BE = function readInt32BE (offset, noAssert) {
offset = offset >>> 0
if (!noAssert) checkOffset(offset, 4, this.length)
return (this[offset] << 24) |
(this[offset + 1] << 16) |
(this[offset + 2] << 8) |
(this[offset + 3])
}
Buffer.prototype.readFloatLE = function readFloatLE (offset, noAssert) {
offset = offset >>> 0
if (!noAssert) checkOffset(offset, 4, this.length)
return ieee754.read(this, offset, true, 23, 4)
}
Buffer.prototype.readFloatBE = function readFloatBE (offset, noAssert) {
offset = offset >>> 0
if (!noAssert) checkOffset(offset, 4, this.length)
return ieee754.read(this, offset, false, 23, 4)
}
Buffer.prototype.readDoubleLE = function readDoubleLE (offset, noAssert) {
offset = offset >>> 0
if (!noAssert) checkOffset(offset, 8, this.length)
return ieee754.read(this, offset, true, 52, 8)
}
Buffer.prototype.readDoubleBE = function readDoubleBE (offset, noAssert) {
offset = offset >>> 0
if (!noAssert) checkOffset(offset, 8, this.length)
return ieee754.read(this, offset, false, 52, 8)
}
function checkInt (buf, value, offset, ext, max, min) {
if (!Buffer.isBuffer(buf)) throw new TypeError('"buffer" argument must be a Buffer instance')
if (value > max || value < min) throw new RangeError('"value" argument is out of bounds')
if (offset + ext > buf.length) throw new RangeError('Index out of range')
}
Buffer.prototype.writeUIntLE = function writeUIntLE (value, offset, byteLength, noAssert) {
value = +value
offset = offset >>> 0
byteLength = byteLength >>> 0
if (!noAssert) {
var maxBytes = Math.pow(2, 8 * byteLength) - 1
checkInt(this, value, offset, byteLength, maxBytes, 0)
}
var mul = 1
var i = 0
this[offset] = value & 0xFF
while (++i < byteLength && (mul *= 0x100)) {
this[offset + i] = (value / mul) & 0xFF
}
return offset + byteLength
}
Buffer.prototype.writeUIntBE = function writeUIntBE (value, offset, byteLength, noAssert) {
value = +value
offset = offset >>> 0
byteLength = byteLength >>> 0
if (!noAssert) {
var maxBytes = Math.pow(2, 8 * byteLength) - 1
checkInt(this, value, offset, byteLength, maxBytes, 0)
}
var i = byteLength - 1
var mul = 1
this[offset + i] = value & 0xFF
while (--i >= 0 && (mul *= 0x100)) {
this[offset + i] = (value / mul) & 0xFF
}
return offset + byteLength
}
Buffer.prototype.writeUInt8 = function writeUInt8 (value, offset, noAssert) {
value = +value
offset = offset >>> 0
if (!noAssert) checkInt(this, value, offset, 1, 0xff, 0)
this[offset] = (value & 0xff)
return offset + 1
}
Buffer.prototype.writeUInt16LE = function writeUInt16LE (value, offset, noAssert) {
value = +value
offset = offset >>> 0
if (!noAssert) checkInt(this, value, offset, 2, 0xffff, 0)
this[offset] = (value & 0xff)
this[offset + 1] = (value >>> 8)
return offset + 2
}
Buffer.prototype.writeUInt16BE = function writeUInt16BE (value, offset, noAssert) {
value = +value
offset = offset >>> 0
if (!noAssert) checkInt(this, value, offset, 2, 0xffff, 0)
this[offset] = (value >>> 8)
this[offset + 1] = (value & 0xff)
return offset + 2
}
Buffer.prototype.writeUInt32LE = function writeUInt32LE (value, offset, noAssert) {
value = +value
offset = offset >>> 0
if (!noAssert) checkInt(this, value, offset, 4, 0xffffffff, 0)
this[offset + 3] = (value >>> 24)
this[offset + 2] = (value >>> 16)
this[offset + 1] = (value >>> 8)
this[offset] = (value & 0xff)
return offset + 4
}
Buffer.prototype.writeUInt32BE = function writeUInt32BE (value, offset, noAssert) {
value = +value
offset = offset >>> 0
if (!noAssert) checkInt(this, value, offset, 4, 0xffffffff, 0)
this[offset] = (value >>> 24)
this[offset + 1] = (value >>> 16)
this[offset + 2] = (value >>> 8)
this[offset + 3] = (value & 0xff)
return offset + 4
}
Buffer.prototype.writeIntLE = function writeIntLE (value, offset, byteLength, noAssert) {
value = +value
offset = offset >>> 0
if (!noAssert) {
var limit = Math.pow(2, (8 * byteLength) - 1)
checkInt(this, value, offset, byteLength, limit - 1, -limit)
}
var i = 0
var mul = 1
var sub = 0
this[offset] = value & 0xFF
while (++i < byteLength && (mul *= 0x100)) {
if (value < 0 && sub === 0 && this[offset + i - 1] !== 0) {
sub = 1
}
this[offset + i] = ((value / mul) >> 0) - sub & 0xFF
}
return offset + byteLength
}
Buffer.prototype.writeIntBE = function writeIntBE (value, offset, byteLength, noAssert) {
value = +value
offset = offset >>> 0
if (!noAssert) {
var limit = Math.pow(2, (8 * byteLength) - 1)
checkInt(this, value, offset, byteLength, limit - 1, -limit)
}
var i = byteLength - 1
var mul = 1
var sub = 0
this[offset + i] = value & 0xFF
while (--i >= 0 && (mul *= 0x100)) {
if (value < 0 && sub === 0 && this[offset + i + 1] !== 0) {
sub = 1
}
this[offset + i] = ((value / mul) >> 0) - sub & 0xFF
}
return offset + byteLength
}
Buffer.prototype.writeInt8 = function writeInt8 (value, offset, noAssert) {
value = +value
offset = offset >>> 0
if (!noAssert) checkInt(this, value, offset, 1, 0x7f, -0x80)
if (value < 0) value = 0xff + value + 1
this[offset] = (value & 0xff)
return offset + 1
}
Buffer.prototype.writeInt16LE = function writeInt16LE (value, offset, noAssert) {
value = +value
offset = offset >>> 0
if (!noAssert) checkInt(this, value, offset, 2, 0x7fff, -0x8000)
this[offset] = (value & 0xff)
this[offset + 1] = (value >>> 8)
return offset + 2
}
Buffer.prototype.writeInt16BE = function writeInt16BE (value, offset, noAssert) {
value = +value
offset = offset >>> 0
if (!noAssert) checkInt(this, value, offset, 2, 0x7fff, -0x8000)
this[offset] = (value >>> 8)
this[offset + 1] = (value & 0xff)
return offset + 2
}
Buffer.prototype.writeInt32LE = function writeInt32LE (value, offset, noAssert) {
value = +value
offset = offset >>> 0
if (!noAssert) checkInt(this, value, offset, 4, 0x7fffffff, -0x80000000)
this[offset] = (value & 0xff)
this[offset + 1] = (value >>> 8)
this[offset + 2] = (value >>> 16)
this[offset + 3] = (value >>> 24)
return offset + 4
}
Buffer.prototype.writeInt32BE = function writeInt32BE (value, offset, noAssert) {
value = +value
offset = offset >>> 0
if (!noAssert) checkInt(this, value, offset, 4, 0x7fffffff, -0x80000000)
if (value < 0) value = 0xffffffff + value + 1
this[offset] = (value >>> 24)
this[offset + 1] = (value >>> 16)
this[offset + 2] = (value >>> 8)
this[offset + 3] = (value & 0xff)
return offset + 4
}
function checkIEEE754 (buf, value, offset, ext, max, min) {
if (offset + ext > buf.length) throw new RangeError('Index out of range')
if (offset < 0) throw new RangeError('Index out of range')
}
function writeFloat (buf, value, offset, littleEndian, noAssert) {
value = +value
offset = offset >>> 0
if (!noAssert) {
checkIEEE754(buf, value, offset, 4, 3.4028234663852886e+38, -3.4028234663852886e+38)
}
ieee754.write(buf, value, offset, littleEndian, 23, 4)
return offset + 4
}
Buffer.prototype.writeFloatLE = function writeFloatLE (value, offset, noAssert) {
return writeFloat(this, value, offset, true, noAssert)
}
Buffer.prototype.writeFloatBE = function writeFloatBE (value, offset, noAssert) {
return writeFloat(this, value, offset, false, noAssert)
}
function writeDouble (buf, value, offset, littleEndian, noAssert) {
value = +value
offset = offset >>> 0
if (!noAssert) {
checkIEEE754(buf, value, offset, 8, 1.7976931348623157E+308, -1.7976931348623157E+308)
}
ieee754.write(buf, value, offset, littleEndian, 52, 8)
return offset + 8
}
Buffer.prototype.writeDoubleLE = function writeDoubleLE (value, offset, noAssert) {
return writeDouble(this, value, offset, true, noAssert)
}
Buffer.prototype.writeDoubleBE = function writeDoubleBE (value, offset, noAssert) {
return writeDouble(this, value, offset, false, noAssert)
}
// copy(targetBuffer, targetStart=0, sourceStart=0, sourceEnd=buffer.length)
Buffer.prototype.copy = function copy (target, targetStart, start, end) {
if (!Buffer.isBuffer(target)) throw new TypeError('argument should be a Buffer')
if (!start) start = 0
if (!end && end !== 0) end = this.length
if (targetStart >= target.length) targetStart = target.length
if (!targetStart) targetStart = 0
if (end > 0 && end < start) end = start
// Copy 0 bytes; we're done
if (end === start) return 0
if (target.length === 0 || this.length === 0) return 0
// Fatal error conditions
if (targetStart < 0) {
throw new RangeError('targetStart out of bounds')
}
if (start < 0 || start >= this.length) throw new RangeError('Index out of range')
if (end < 0) throw new RangeError('sourceEnd out of bounds')
// Are we oob?
if (end > this.length) end = this.length
if (target.length - targetStart < end - start) {
end = target.length - targetStart + start
}
var len = end - start
if (this === target && typeof Uint8Array.prototype.copyWithin === 'function') {
// Use built-in when available, missing from IE11
this.copyWithin(targetStart, start, end)
} else if (this === target && start < targetStart && targetStart < end) {
// descending copy from end
for (var i = len - 1; i >= 0; --i) {
target[i + targetStart] = this[i + start]
}
} else {
Uint8Array.prototype.set.call(
target,
this.subarray(start, end),
targetStart
)
}
return len
}
// Usage:
// buffer.fill(number[, offset[, end]])
// buffer.fill(buffer[, offset[, end]])
// buffer.fill(string[, offset[, end]][, encoding])
Buffer.prototype.fill = function fill (val, start, end, encoding) {
// Handle string cases:
if (typeof val === 'string') {
if (typeof start === 'string') {
encoding = start
start = 0
end = this.length
} else if (typeof end === 'string') {
encoding = end
end = this.length
}
if (encoding !== undefined && typeof encoding !== 'string') {
throw new TypeError('encoding must be a string')
}
if (typeof encoding === 'string' && !Buffer.isEncoding(encoding)) {
throw new TypeError('Unknown encoding: ' + encoding)
}
if (val.length === 1) {
var code = val.charCodeAt(0)
if ((encoding === 'utf8' && code < 128) ||
encoding === 'latin1') {
// Fast path: If `val` fits into a single byte, use that numeric value.
val = code
}
}
} else if (typeof val === 'number') {
val = val & 255
}
// Invalid ranges are not set to a default, so can range check early.
if (start < 0 || this.length < start || this.length < end) {
throw new RangeError('Out of range index')
}
if (end <= start) {
return this
}
start = start >>> 0
end = end === undefined ? this.length : end >>> 0
if (!val) val = 0
var i
if (typeof val === 'number') {
for (i = start; i < end; ++i) {
this[i] = val
}
} else {
var bytes = Buffer.isBuffer(val)
? val
: Buffer.from(val, encoding)
var len = bytes.length
if (len === 0) {
throw new TypeError('The value "' + val +
'" is invalid for argument "value"')
}
for (i = 0; i < end - start; ++i) {
this[i + start] = bytes[i % len]
}
}
return this
}
// HELPER FUNCTIONS
// ================
var INVALID_BASE64_RE = /[^+/0-9A-Za-z-_]/g
function base64clean (str) {
// Node takes equal signs as end of the Base64 encoding
str = str.split('=')[0]
// Node strips out invalid characters like \n and \t from the string, base64-js does not
str = str.trim().replace(INVALID_BASE64_RE, '')
// Node converts strings with length < 2 to ''
if (str.length < 2) return ''
// Node allows for non-padded base64 strings (missing trailing ===), base64-js does not
while (str.length % 4 !== 0) {
str = str + '='
}
return str
}
function toHex (n) {
if (n < 16) return '0' + n.toString(16)
return n.toString(16)
}
function utf8ToBytes (string, units) {
units = units || Infinity
var codePoint
var length = string.length
var leadSurrogate = null
var bytes = []
for (var i = 0; i < length; ++i) {
codePoint = string.charCodeAt(i)
// is surrogate component
if (codePoint > 0xD7FF && codePoint < 0xE000) {
// last char was a lead
if (!leadSurrogate) {
// no lead yet
if (codePoint > 0xDBFF) {
// unexpected trail
if ((units -= 3) > -1) bytes.push(0xEF, 0xBF, 0xBD)
continue
} else if (i + 1 === length) {
// unpaired lead
if ((units -= 3) > -1) bytes.push(0xEF, 0xBF, 0xBD)
continue
}
// valid lead
leadSurrogate = codePoint
continue
}
// 2 leads in a row
if (codePoint < 0xDC00) {
if ((units -= 3) > -1) bytes.push(0xEF, 0xBF, 0xBD)
leadSurrogate = codePoint
continue
}
// valid surrogate pair
codePoint = (leadSurrogate - 0xD800 << 10 | codePoint - 0xDC00) + 0x10000
} else if (leadSurrogate) {
// valid bmp char, but last char was a lead
if ((units -= 3) > -1) bytes.push(0xEF, 0xBF, 0xBD)
}
leadSurrogate = null
// encode utf8
if (codePoint < 0x80) {
if ((units -= 1) < 0) break
bytes.push(codePoint)
} else if (codePoint < 0x800) {
if ((units -= 2) < 0) break
bytes.push(
codePoint >> 0x6 | 0xC0,
codePoint & 0x3F | 0x80
)
} else if (codePoint < 0x10000) {
if ((units -= 3) < 0) break
bytes.push(
codePoint >> 0xC | 0xE0,
codePoint >> 0x6 & 0x3F | 0x80,
codePoint & 0x3F | 0x80
)
} else if (codePoint < 0x110000) {
if ((units -= 4) < 0) break
bytes.push(
codePoint >> 0x12 | 0xF0,
codePoint >> 0xC & 0x3F | 0x80,
codePoint >> 0x6 & 0x3F | 0x80,
codePoint & 0x3F | 0x80
)
} else {
throw new Error('Invalid code point')
}
}
return bytes
}
function asciiToBytes (str) {
var byteArray = []
for (var i = 0; i < str.length; ++i) {
// Node's code seems to be doing this and not & 0x7F..
byteArray.push(str.charCodeAt(i) & 0xFF)
}
return byteArray
}
function utf16leToBytes (str, units) {
var c, hi, lo
var byteArray = []
for (var i = 0; i < str.length; ++i) {
if ((units -= 2) < 0) break
c = str.charCodeAt(i)
hi = c >> 8
lo = c % 256
byteArray.push(lo)
byteArray.push(hi)
}
return byteArray
}
function base64ToBytes (str) {
return base64.toByteArray(base64clean(str))
}
function blitBuffer (src, dst, offset, length) {
for (var i = 0; i < length; ++i) {
if ((i + offset >= dst.length) || (i >= src.length)) break
dst[i + offset] = src[i]
}
return i
}
// ArrayBuffer or Uint8Array objects from other contexts (i.e. iframes) do not pass
// the `instanceof` check but they should be treated as of that type.
// See: https://github.com/feross/buffer/issues/166
function isInstance (obj, type) {
return obj instanceof type ||
(obj != null && obj.constructor != null && obj.constructor.name != null &&
obj.constructor.name === type.name)
}
function numberIsNaN (obj) {
// For IE11 support
return obj !== obj // eslint-disable-line no-self-compare
}
}).call(this)}).call(this,_glvis_("buffer").Buffer)
},{"base64-js":1,"buffer":3,"ieee754":4}],4:[function(_glvis_,module,exports){
exports.read = function (buffer, offset, isLE, mLen, nBytes) {
var e, m
var eLen = (nBytes * 8) - mLen - 1
var eMax = (1 << eLen) - 1
var eBias = eMax >> 1
var nBits = -7
var i = isLE ? (nBytes - 1) : 0
var d = isLE ? -1 : 1
var s = buffer[offset + i]
i += d
e = s & ((1 << (-nBits)) - 1)
s >>= (-nBits)
nBits += eLen
for (; nBits > 0; e = (e * 256) + buffer[offset + i], i += d, nBits -= 8) {}
m = e & ((1 << (-nBits)) - 1)
e >>= (-nBits)
nBits += mLen
for (; nBits > 0; m = (m * 256) + buffer[offset + i], i += d, nBits -= 8) {}
if (e === 0) {
e = 1 - eBias
} else if (e === eMax) {
return m ? NaN : ((s ? -1 : 1) * Infinity)
} else {
m = m + Math.pow(2, mLen)
e = e - eBias
}
return (s ? -1 : 1) * m * Math.pow(2, e - mLen)
}
exports.write = function (buffer, value, offset, isLE, mLen, nBytes) {
var e, m, c
var eLen = (nBytes * 8) - mLen - 1
var eMax = (1 << eLen) - 1
var eBias = eMax >> 1
var rt = (mLen === 23 ? Math.pow(2, -24) - Math.pow(2, -77) : 0)
var i = isLE ? 0 : (nBytes - 1)
var d = isLE ? 1 : -1
var s = value < 0 || (value === 0 && 1 / value < 0) ? 1 : 0
value = Math.abs(value)
if (isNaN(value) || value === Infinity) {
m = isNaN(value) ? 1 : 0
e = eMax
} else {
e = Math.floor(Math.log(value) / Math.LN2)
if (value * (c = Math.pow(2, -e)) < 1) {
e--
c *= 2
}
if (e + eBias >= 1) {
value += rt / c
} else {
value += rt * Math.pow(2, 1 - eBias)
}
if (value * c >= 2) {
e++
c /= 2
}
if (e + eBias >= eMax) {
m = 0
e = eMax
} else if (e + eBias >= 1) {
m = ((value * c) - 1) * Math.pow(2, mLen)
e = e + eBias
} else {
m = value * Math.pow(2, eBias - 1) * Math.pow(2, mLen)
e = 0
}
}
for (; mLen >= 8; buffer[offset + i] = m & 0xff, i += d, m /= 256, mLen -= 8) {}
e = (e << mLen) | m
eLen += mLen
for (; eLen > 0; buffer[offset + i] = e & 0xff, i += d, e /= 256, eLen -= 8) {}
buffer[offset + i - d] |= s * 128
}
},{}],5:[function(_glvis_,module,exports){
// shim for using process in browser
var process = module.exports = {};
// cached from whatever global is present so that test runners that stub it
// don't break things. But we need to wrap it in a try catch in case it is
// wrapped in strict mode code which doesn't define any globals. It's inside a
// function because try/catches deoptimize in certain engines.
var cachedSetTimeout;
var cachedClearTimeout;
function defaultSetTimout() {
throw new Error('setTimeout has not been defined');
}
function defaultClearTimeout () {
throw new Error('clearTimeout has not been defined');
}
(function () {
try {
if (typeof setTimeout === 'function') {
cachedSetTimeout = setTimeout;
} else {
cachedSetTimeout = defaultSetTimout;
}
} catch (e) {
cachedSetTimeout = defaultSetTimout;
}
try {
if (typeof clearTimeout === 'function') {
cachedClearTimeout = clearTimeout;
} else {
cachedClearTimeout = defaultClearTimeout;
}
} catch (e) {
cachedClearTimeout = defaultClearTimeout;
}
} ())
function runTimeout(fun) {
if (cachedSetTimeout === setTimeout) {
//normal enviroments in sane situations
return setTimeout(fun, 0);
}
// if setTimeout wasn't available but was latter defined
if ((cachedSetTimeout === defaultSetTimout || !cachedSetTimeout) && setTimeout) {
cachedSetTimeout = setTimeout;
return setTimeout(fun, 0);
}
try {
// when when somebody has screwed with setTimeout but no I.E. maddness
return cachedSetTimeout(fun, 0);
} catch(e){
try {
// When we are in I.E. but the script has been evaled so I.E. doesn't trust the global object when called normally
return cachedSetTimeout.call(null, fun, 0);
} catch(e){
// same as above but when it's a version of I.E. that must have the global object for 'this', hopfully our context correct otherwise it will throw a global error
return cachedSetTimeout.call(this, fun, 0);
}
}
}
function runClearTimeout(marker) {
if (cachedClearTimeout === clearTimeout) {
//normal enviroments in sane situations
return clearTimeout(marker);
}
// if clearTimeout wasn't available but was latter defined
if ((cachedClearTimeout === defaultClearTimeout || !cachedClearTimeout) && clearTimeout) {
cachedClearTimeout = clearTimeout;
return clearTimeout(marker);
}
try {
// when when somebody has screwed with setTimeout but no I.E. maddness
return cachedClearTimeout(marker);
} catch (e){
try {
// When we are in I.E. but the script has been evaled so I.E. doesn't trust the global object when called normally
return cachedClearTimeout.call(null, marker);
} catch (e){
// same as above but when it's a version of I.E. that must have the global object for 'this', hopfully our context correct otherwise it will throw a global error.
// Some versions of I.E. have different rules for clearTimeout vs setTimeout
return cachedClearTimeout.call(this, marker);
}
}
}
var queue = [];
var draining = false;
var currentQueue;
var queueIndex = -1;
function cleanUpNextTick() {
if (!draining || !currentQueue) {
return;
}
draining = false;
if (currentQueue.length) {
queue = currentQueue.concat(queue);
} else {
queueIndex = -1;
}
if (queue.length) {
drainQueue();
}
}
function drainQueue() {
if (draining) {
return;
}
var timeout = runTimeout(cleanUpNextTick);
draining = true;
var len = queue.length;
while(len) {
currentQueue = queue;
queue = [];
while (++queueIndex < len) {
if (currentQueue) {
currentQueue[queueIndex].run();
}
}
queueIndex = -1;
len = queue.length;
}
currentQueue = null;
draining = false;
runClearTimeout(timeout);
}
process.nextTick = function (fun) {
var args = new Array(arguments.length - 1);
if (arguments.length > 1) {
for (var i = 1; i < arguments.length; i++) {
args[i - 1] = arguments[i];
}
}
queue.push(new Item(fun, args));
if (queue.length === 1 && !draining) {
runTimeout(drainQueue);
}
};
// v8 likes predictible objects
function Item(fun, array) {
this.fun = fun;
this.array = array;
}
Item.prototype.run = function () {
this.fun.apply(null, this.array);
};
process.title = 'browser';
process.browser = true;
process.env = {};
process.argv = [];
process.version = ''; // empty string to avoid regexp issues
process.versions = {};
function noop() {}
process.on = noop;
process.addListener = noop;
process.once = noop;
process.off = noop;
process.removeListener = noop;
process.removeAllListeners = noop;
process.emit = noop;
process.prependListener = noop;
process.prependOnceListener = noop;
process.listeners = function (name) { return [] }
process.binding = function (name) {
throw new Error('process.binding is not supported');
};
process.cwd = function () { return '/' };
process.chdir = function (dir) {
throw new Error('process.chdir is not supported');
};
process.umask = function() { return 0; };
},{}],6:[function(_glvis_,module,exports){
module.exports = {
alpha_shape: _glvis_('alpha-shape'),
convex_hull: _glvis_('convex-hull'),
delaunay_triangulate: _glvis_('delaunay-triangulate'),
gl_cone3d: _glvis_('gl-cone3d'),
gl_error3d: _glvis_('gl-error3d'),
gl_heatmap2d: _glvis_('gl-heatmap2d'),
gl_line3d: _glvis_('gl-line3d'),
gl_mesh3d: _glvis_('gl-mesh3d'),
gl_plot2d: _glvis_('gl-plot2d'),
gl_plot3d: _glvis_('gl-plot3d'),
gl_pointcloud2d: _glvis_('gl-pointcloud2d'),
gl_scatter3d: _glvis_('gl-scatter3d'),
gl_select_box: _glvis_('gl-select-box'),
gl_spikes2d: _glvis_('gl-spikes2d'),
gl_streamtube3d: _glvis_('gl-streamtube3d'),
gl_surface3d: _glvis_('gl-surface3d'),
ndarray: _glvis_('ndarray'),
ndarray_linear_interpolate: _glvis_('ndarray-linear-interpolate'),
};
},{"alpha-shape":12,"convex-hull":58,"delaunay-triangulate":63,"gl-cone3d":79,"gl-error3d":84,"gl-heatmap2d":88,"gl-line3d":91,"gl-mesh3d":112,"gl-plot2d":118,"gl-plot3d":121,"gl-pointcloud2d":123,"gl-scatter3d":128,"gl-select-box":130,"gl-spikes2d":139,"gl-streamtube3d":143,"gl-surface3d":145,"ndarray":259,"ndarray-linear-interpolate":253}],7:[function(_glvis_,module,exports){
'use strict'
module.exports = createViewController
var createTurntable = _glvis_('turntable-camera-controller')
var createOrbit = _glvis_('orbit-camera-controller')
var createMatrix = _glvis_('matrix-camera-controller')
function ViewController(controllers, mode) {
this._controllerNames = Object.keys(controllers)
this._controllerList = this._controllerNames.map(function(n) {
return controllers[n]
})
this._mode = mode
this._active = controllers[mode]
if(!this._active) {
this._mode = 'turntable'
this._active = controllers.turntable
}
this.modes = this._controllerNames
this.computedMatrix = this._active.computedMatrix
this.computedEye = this._active.computedEye
this.computedUp = this._active.computedUp
this.computedCenter = this._active.computedCenter
this.computedRadius = this._active.computedRadius
}
var proto = ViewController.prototype
proto.flush = function(a0) {
var cc = this._controllerList
for (var i = 0; i < cc.length; ++i) {
cc[i].flush(a0)
}
}
proto.idle = function(a0) {
var cc = this._controllerList
for (var i = 0; i < cc.length; ++i) {
cc[i].idle(a0)
}
}
proto.lookAt = function(a0, a1, a2, a3) {
var cc = this._controllerList
for (var i = 0; i < cc.length; ++i) {
cc[i].lookAt(a0, a1, a2, a3)
}
}
proto.rotate = function(a0, a1, a2, a3) {
var cc = this._controllerList
for (var i = 0; i < cc.length; ++i) {
cc[i].rotate(a0, a1, a2, a3)
}
}
proto.pan = function(a0, a1, a2, a3) {
var cc = this._controllerList
for (var i = 0; i < cc.length; ++i) {
cc[i].pan(a0, a1, a2, a3)
}
}
proto.translate = function(a0, a1, a2, a3) {
var cc = this._controllerList
for (var i = 0; i < cc.length; ++i) {
cc[i].translate(a0, a1, a2, a3)
}
}
proto.setMatrix = function(a0, a1) {
var cc = this._controllerList
for (var i = 0; i < cc.length; ++i) {
cc[i].setMatrix(a0, a1)
}
}
proto.setDistanceLimits = function(a0, a1) {
var cc = this._controllerList
for (var i = 0; i < cc.length; ++i) {
cc[i].setDistanceLimits(a0, a1)
}
}
proto.setDistance = function(a0, a1) {
var cc = this._controllerList
for (var i = 0; i < cc.length; ++i) {
cc[i].setDistance(a0, a1)
}
}
proto.recalcMatrix = function(t) {
this._active.recalcMatrix(t)
}
proto.getDistance = function(t) {
return this._active.getDistance(t)
}
proto.getDistanceLimits = function(out) {
return this._active.getDistanceLimits(out)
}
proto.lastT = function() {
return this._active.lastT()
}
proto.setMode = function(mode) {
if(mode === this._mode) {
return
}
var idx = this._controllerNames.indexOf(mode)
if(idx < 0) {
return
}
var prev = this._active
var next = this._controllerList[idx]
var lastT = Math.max(prev.lastT(), next.lastT())
prev.recalcMatrix(lastT)
next.setMatrix(lastT, prev.computedMatrix)
this._active = next
this._mode = mode
//Update matrix properties
this.computedMatrix = this._active.computedMatrix
this.computedEye = this._active.computedEye
this.computedUp = this._active.computedUp
this.computedCenter = this._active.computedCenter
this.computedRadius = this._active.computedRadius
}
proto.getMode = function() {
return this._mode
}
function createViewController(options) {
options = options || {}
var eye = options.eye || [0,0,1]
var center = options.center || [0,0,0]
var up = options.up || [0,1,0]
var limits = options.distanceLimits || [0, Infinity]
var mode = options.mode || 'turntable'
var turntable = createTurntable()
var orbit = createOrbit()
var matrix = createMatrix()
turntable.setDistanceLimits(limits[0], limits[1])
turntable.lookAt(0, eye, center, up)
orbit.setDistanceLimits(limits[0], limits[1])
orbit.lookAt(0, eye, center, up)
matrix.setDistanceLimits(limits[0], limits[1])
matrix.lookAt(0, eye, center, up)
return new ViewController({
turntable: turntable,
orbit: orbit,
matrix: matrix
}, mode)
}
},{"matrix-camera-controller":245,"orbit-camera-controller":263,"turntable-camera-controller":305}],8:[function(_glvis_,module,exports){
'use strict'
var weakMap = typeof WeakMap === 'undefined' ? _glvis_('weak-map') : WeakMap
var createBuffer = _glvis_('gl-buffer')
var createVAO = _glvis_('gl-vao')
var TriangleCache = new weakMap()
function createABigTriangle(gl) {
var triangleVAO = TriangleCache.get(gl)
var handle = triangleVAO && (triangleVAO._triangleBuffer.handle || triangleVAO._triangleBuffer.buffer)
if(!handle || !gl.isBuffer(handle)) {
var buf = createBuffer(gl, new Float32Array([-1, -1, -1, 4, 4, -1]))
triangleVAO = createVAO(gl, [
{ buffer: buf,
type: gl.FLOAT,
size: 2
}
])
triangleVAO._triangleBuffer = buf
TriangleCache.set(gl, triangleVAO)
}
triangleVAO.bind()
gl.drawArrays(gl.TRIANGLES, 0, 3)
triangleVAO.unbind()
}
module.exports = createABigTriangle
},{"gl-buffer":78,"gl-vao":150,"weak-map":313}],9:[function(_glvis_,module,exports){
var padLeft = _glvis_('pad-left')
module.exports = addLineNumbers
function addLineNumbers (string, start, delim) {
start = typeof start === 'number' ? start : 1
delim = delim || ': '
var lines = string.split(/\r?\n/)
var totalDigits = String(lines.length + start - 1).length
return lines.map(function (line, i) {
var c = i + start
var digits = String(c).length
var prefix = padLeft(c, totalDigits - digits)
return prefix + delim + line
}).join('\n')
}
},{"pad-left":264}],10:[function(_glvis_,module,exports){
'use strict'
module.exports = affineHull
var orient = _glvis_('robust-orientation')
function linearlyIndependent(points, d) {
var nhull = new Array(d+1)
for(var i=0; i 0) {
a = a.ushln(shift)
} else if(shift < 0) {
b = b.ushln(-shift)
}
return rationalize(a, b)
}
},{"./div":17,"./is-rat":19,"./lib/is-bn":23,"./lib/num-to-bn":24,"./lib/rationalize":25,"./lib/str-to-bn":26}],19:[function(_glvis_,module,exports){
'use strict'
var isBN = _glvis_('./lib/is-bn')
module.exports = isRat
function isRat(x) {
return Array.isArray(x) && x.length === 2 && isBN(x[0]) && isBN(x[1])
}
},{"./lib/is-bn":23}],20:[function(_glvis_,module,exports){
'use strict'
var BN = _glvis_('bn.js')
module.exports = sign
function sign (x) {
return x.cmp(new BN(0))
}
},{"bn.js":33}],21:[function(_glvis_,module,exports){
'use strict'
var sign = _glvis_('./bn-sign')
module.exports = bn2num
//TODO: Make this better
function bn2num(b) {
var l = b.length
var words = b.words
var out = 0
if (l === 1) {
out = words[0]
} else if (l === 2) {
out = words[0] + (words[1] * 0x4000000)
} else {
for (var i = 0; i < l; i++) {
var w = words[i]
out += w * Math.pow(0x4000000, i)
}
}
return sign(b) * out
}
},{"./bn-sign":20}],22:[function(_glvis_,module,exports){
'use strict'
var db = _glvis_('double-bits')
var ctz = _glvis_('bit-twiddle').countTrailingZeros
module.exports = ctzNumber
//Counts the number of trailing zeros
function ctzNumber(x) {
var l = ctz(db.lo(x))
if(l < 32) {
return l
}
var h = ctz(db.hi(x))
if(h > 20) {
return 52
}
return h + 32
}
},{"bit-twiddle":32,"double-bits":64}],23:[function(_glvis_,module,exports){
'use strict'
var BN = _glvis_('bn.js')
module.exports = isBN
//Test if x is a bignumber
//FIXME: obviously this is the wrong way to do it
function isBN(x) {
return x && typeof x === 'object' && Boolean(x.words)
}
},{"bn.js":33}],24:[function(_glvis_,module,exports){
'use strict'
var BN = _glvis_('bn.js')
var db = _glvis_('double-bits')
module.exports = num2bn
function num2bn(x) {
var e = db.exponent(x)
if(e < 52) {
return new BN(x)
} else {
return (new BN(x * Math.pow(2, 52-e))).ushln(e-52)
}
}
},{"bn.js":33,"double-bits":64}],25:[function(_glvis_,module,exports){
'use strict'
var num2bn = _glvis_('./num-to-bn')
var sign = _glvis_('./bn-sign')
module.exports = rationalize
function rationalize(numer, denom) {
var snumer = sign(numer)
var sdenom = sign(denom)
if(snumer === 0) {
return [num2bn(0), num2bn(1)]
}
if(sdenom === 0) {
return [num2bn(0), num2bn(0)]
}
if(sdenom < 0) {
numer = numer.neg()
denom = denom.neg()
}
var d = numer.gcd(denom)
if(d.cmpn(1)) {
return [ numer.div(d), denom.div(d) ]
}
return [ numer, denom ]
}
},{"./bn-sign":20,"./num-to-bn":24}],26:[function(_glvis_,module,exports){
'use strict'
var BN = _glvis_('bn.js')
module.exports = str2BN
function str2BN(x) {
return new BN(x)
}
},{"bn.js":33}],27:[function(_glvis_,module,exports){
'use strict'
var rationalize = _glvis_('./lib/rationalize')
module.exports = mul
function mul(a, b) {
return rationalize(a[0].mul(b[0]), a[1].mul(b[1]))
}
},{"./lib/rationalize":25}],28:[function(_glvis_,module,exports){
'use strict'
var bnsign = _glvis_('./lib/bn-sign')
module.exports = sign
function sign(x) {
return bnsign(x[0]) * bnsign(x[1])
}
},{"./lib/bn-sign":20}],29:[function(_glvis_,module,exports){
'use strict'
var rationalize = _glvis_('./lib/rationalize')
module.exports = sub
function sub(a, b) {
return rationalize(a[0].mul(b[1]).sub(a[1].mul(b[0])), a[1].mul(b[1]))
}
},{"./lib/rationalize":25}],30:[function(_glvis_,module,exports){
'use strict'
var bn2num = _glvis_('./lib/bn-to-num')
var ctz = _glvis_('./lib/ctz')
module.exports = roundRat
// Round a rational to the closest float
function roundRat (f) {
var a = f[0]
var b = f[1]
if (a.cmpn(0) === 0) {
return 0
}
var h = a.abs().divmod(b.abs())
var iv = h.div
var x = bn2num(iv)
var ir = h.mod
var sgn = (a.negative !== b.negative) ? -1 : 1
if (ir.cmpn(0) === 0) {
return sgn * x
}
if (x) {
var s = ctz(x) + 4
var y = bn2num(ir.ushln(s).divRound(b))
return sgn * (x + y * Math.pow(2, -s))
} else {
var ybits = b.bitLength() - ir.bitLength() + 53
var y = bn2num(ir.ushln(ybits).divRound(b))
if (ybits < 1023) {
return sgn * y * Math.pow(2, -ybits)
}
y *= Math.pow(2, -1023)
return sgn * y * Math.pow(2, 1023 - ybits)
}
}
},{"./lib/bn-to-num":21,"./lib/ctz":22}],31:[function(_glvis_,module,exports){
"use strict"
// (a, y, c, l, h) = (array, y[, cmp, lo, hi])
function ge(a, y, c, l, h) {
var i = h + 1;
while (l <= h) {
var m = (l + h) >>> 1, x = a[m];
var p = (c !== undefined) ? c(x, y) : (x - y);
if (p >= 0) { i = m; h = m - 1 } else { l = m + 1 }
}
return i;
};
function gt(a, y, c, l, h) {
var i = h + 1;
while (l <= h) {
var m = (l + h) >>> 1, x = a[m];
var p = (c !== undefined) ? c(x, y) : (x - y);
if (p > 0) { i = m; h = m - 1 } else { l = m + 1 }
}
return i;
};
function lt(a, y, c, l, h) {
var i = l - 1;
while (l <= h) {
var m = (l + h) >>> 1, x = a[m];
var p = (c !== undefined) ? c(x, y) : (x - y);
if (p < 0) { i = m; l = m + 1 } else { h = m - 1 }
}
return i;
};
function le(a, y, c, l, h) {
var i = l - 1;
while (l <= h) {
var m = (l + h) >>> 1, x = a[m];
var p = (c !== undefined) ? c(x, y) : (x - y);
if (p <= 0) { i = m; l = m + 1 } else { h = m - 1 }
}
return i;
};
function eq(a, y, c, l, h) {
while (l <= h) {
var m = (l + h) >>> 1, x = a[m];
var p = (c !== undefined) ? c(x, y) : (x - y);
if (p === 0) { return m }
if (p <= 0) { l = m + 1 } else { h = m - 1 }
}
return -1;
};
function norm(a, y, c, l, h, f) {
if (typeof c === 'function') {
return f(a, y, c, (l === undefined) ? 0 : l | 0, (h === undefined) ? a.length - 1 : h | 0);
}
return f(a, y, undefined, (c === undefined) ? 0 : c | 0, (l === undefined) ? a.length - 1 : l | 0);
}
module.exports = {
ge: function(a, y, c, l, h) { return norm(a, y, c, l, h, ge)},
gt: function(a, y, c, l, h) { return norm(a, y, c, l, h, gt)},
lt: function(a, y, c, l, h) { return norm(a, y, c, l, h, lt)},
le: function(a, y, c, l, h) { return norm(a, y, c, l, h, le)},
eq: function(a, y, c, l, h) { return norm(a, y, c, l, h, eq)}
}
},{}],32:[function(_glvis_,module,exports){
/**
* Bit twiddling hacks for JavaScript.
*
* Author: Mikola Lysenko
*
* Ported from Stanford bit twiddling hack library:
* http://graphics.stanford.edu/~seander/bithacks.html
*/
"use strict"; "use restrict";
//Number of bits in an integer
var INT_BITS = 32;
//Constants
exports.INT_BITS = INT_BITS;
exports.INT_MAX = 0x7fffffff;
exports.INT_MIN = -1<<(INT_BITS-1);
//Returns -1, 0, +1 depending on sign of x
exports.sign = function(v) {
return (v > 0) - (v < 0);
}
//Computes absolute value of integer
exports.abs = function(v) {
var mask = v >> (INT_BITS-1);
return (v ^ mask) - mask;
}
//Computes minimum of integers x and y
exports.min = function(x, y) {
return y ^ ((x ^ y) & -(x < y));
}
//Computes maximum of integers x and y
exports.max = function(x, y) {
return x ^ ((x ^ y) & -(x < y));
}
//Checks if a number is a power of two
exports.isPow2 = function(v) {
return !(v & (v-1)) && (!!v);
}
//Computes log base 2 of v
exports.log2 = function(v) {
var r, shift;
r = (v > 0xFFFF) << 4; v >>>= r;
shift = (v > 0xFF ) << 3; v >>>= shift; r |= shift;
shift = (v > 0xF ) << 2; v >>>= shift; r |= shift;
shift = (v > 0x3 ) << 1; v >>>= shift; r |= shift;
return r | (v >> 1);
}
//Computes log base 10 of v
exports.log10 = function(v) {
return (v >= 1000000000) ? 9 : (v >= 100000000) ? 8 : (v >= 10000000) ? 7 :
(v >= 1000000) ? 6 : (v >= 100000) ? 5 : (v >= 10000) ? 4 :
(v >= 1000) ? 3 : (v >= 100) ? 2 : (v >= 10) ? 1 : 0;
}
//Counts number of bits
exports.popCount = function(v) {
v = v - ((v >>> 1) & 0x55555555);
v = (v & 0x33333333) + ((v >>> 2) & 0x33333333);
return ((v + (v >>> 4) & 0xF0F0F0F) * 0x1010101) >>> 24;
}
//Counts number of trailing zeros
function countTrailingZeros(v) {
var c = 32;
v &= -v;
if (v) c--;
if (v & 0x0000FFFF) c -= 16;
if (v & 0x00FF00FF) c -= 8;
if (v & 0x0F0F0F0F) c -= 4;
if (v & 0x33333333) c -= 2;
if (v & 0x55555555) c -= 1;
return c;
}
exports.countTrailingZeros = countTrailingZeros;
//Rounds to next power of 2
exports.nextPow2 = function(v) {
v += v === 0;
--v;
v |= v >>> 1;
v |= v >>> 2;
v |= v >>> 4;
v |= v >>> 8;
v |= v >>> 16;
return v + 1;
}
//Rounds down to previous power of 2
exports.prevPow2 = function(v) {
v |= v >>> 1;
v |= v >>> 2;
v |= v >>> 4;
v |= v >>> 8;
v |= v >>> 16;
return v - (v>>>1);
}
//Computes parity of word
exports.parity = function(v) {
v ^= v >>> 16;
v ^= v >>> 8;
v ^= v >>> 4;
v &= 0xf;
return (0x6996 >>> v) & 1;
}
var REVERSE_TABLE = new Array(256);
(function(tab) {
for(var i=0; i<256; ++i) {
var v = i, r = i, s = 7;
for (v >>>= 1; v; v >>>= 1) {
r <<= 1;
r |= v & 1;
--s;
}
tab[i] = (r << s) & 0xff;
}
})(REVERSE_TABLE);
//Reverse bits in a 32 bit word
exports.reverse = function(v) {
return (REVERSE_TABLE[ v & 0xff] << 24) |
(REVERSE_TABLE[(v >>> 8) & 0xff] << 16) |
(REVERSE_TABLE[(v >>> 16) & 0xff] << 8) |
REVERSE_TABLE[(v >>> 24) & 0xff];
}
//Interleave bits of 2 coordinates with 16 bits. Useful for fast quadtree codes
exports.interleave2 = function(x, y) {
x &= 0xFFFF;
x = (x | (x << 8)) & 0x00FF00FF;
x = (x | (x << 4)) & 0x0F0F0F0F;
x = (x | (x << 2)) & 0x33333333;
x = (x | (x << 1)) & 0x55555555;
y &= 0xFFFF;
y = (y | (y << 8)) & 0x00FF00FF;
y = (y | (y << 4)) & 0x0F0F0F0F;
y = (y | (y << 2)) & 0x33333333;
y = (y | (y << 1)) & 0x55555555;
return x | (y << 1);
}
//Extracts the nth interleaved component
exports.deinterleave2 = function(v, n) {
v = (v >>> n) & 0x55555555;
v = (v | (v >>> 1)) & 0x33333333;
v = (v | (v >>> 2)) & 0x0F0F0F0F;
v = (v | (v >>> 4)) & 0x00FF00FF;
v = (v | (v >>> 16)) & 0x000FFFF;
return (v << 16) >> 16;
}
//Interleave bits of 3 coordinates, each with 10 bits. Useful for fast octree codes
exports.interleave3 = function(x, y, z) {
x &= 0x3FF;
x = (x | (x<<16)) & 4278190335;
x = (x | (x<<8)) & 251719695;
x = (x | (x<<4)) & 3272356035;
x = (x | (x<<2)) & 1227133513;
y &= 0x3FF;
y = (y | (y<<16)) & 4278190335;
y = (y | (y<<8)) & 251719695;
y = (y | (y<<4)) & 3272356035;
y = (y | (y<<2)) & 1227133513;
x |= (y << 1);
z &= 0x3FF;
z = (z | (z<<16)) & 4278190335;
z = (z | (z<<8)) & 251719695;
z = (z | (z<<4)) & 3272356035;
z = (z | (z<<2)) & 1227133513;
return x | (z << 2);
}
//Extracts nth interleaved component of a 3-tuple
exports.deinterleave3 = function(v, n) {
v = (v >>> n) & 1227133513;
v = (v | (v>>>2)) & 3272356035;
v = (v | (v>>>4)) & 251719695;
v = (v | (v>>>8)) & 4278190335;
v = (v | (v>>>16)) & 0x3FF;
return (v<<22)>>22;
}
//Computes next combination in colexicographic order (this is mistakenly called nextPermutation on the bit twiddling hacks page)
exports.nextCombination = function(v) {
var t = v | (v - 1);
return (t + 1) | (((~t & -~t) - 1) >>> (countTrailingZeros(v) + 1));
}
},{}],33:[function(_glvis_,module,exports){
(function (module, exports) {
'use strict';
// Utils
function assert (val, msg) {
if (!val) throw new Error(msg || 'Assertion failed');
}
// Could use `inherits` module, but don't want to move from single file
// architecture yet.
function inherits (ctor, superCtor) {
ctor.super_ = superCtor;
var TempCtor = function () {};
TempCtor.prototype = superCtor.prototype;
ctor.prototype = new TempCtor();
ctor.prototype.constructor = ctor;
}
// BN
function BN (number, base, endian) {
if (BN.isBN(number)) {
return number;
}
this.negative = 0;
this.words = null;
this.length = 0;
// Reduction context
this.red = null;
if (number !== null) {
if (base === 'le' || base === 'be') {
endian = base;
base = 10;
}
this._init(number || 0, base || 10, endian || 'be');
}
}
if (typeof module === 'object') {
module.exports = BN;
} else {
exports.BN = BN;
}
BN.BN = BN;
BN.wordSize = 26;
var Buffer;
try {
if (typeof window !== 'undefined' && typeof window.Buffer !== 'undefined') {
Buffer = window.Buffer;
} else {
Buffer = _glvis_('buffer').Buffer;
}
} catch (e) {
}
BN.isBN = function isBN (num) {
if (num instanceof BN) {
return true;
}
return num !== null && typeof num === 'object' &&
num.constructor.wordSize === BN.wordSize && Array.isArray(num.words);
};
BN.max = function max (left, right) {
if (left.cmp(right) > 0) return left;
return right;
};
BN.min = function min (left, right) {
if (left.cmp(right) < 0) return left;
return right;
};
BN.prototype._init = function init (number, base, endian) {
if (typeof number === 'number') {
return this._initNumber(number, base, endian);
}
if (typeof number === 'object') {
return this._initArray(number, base, endian);
}
if (base === 'hex') {
base = 16;
}
assert(base === (base | 0) && base >= 2 && base <= 36);
number = number.toString().replace(/\s+/g, '');
var start = 0;
if (number[0] === '-') {
start++;
this.negative = 1;
}
if (start < number.length) {
if (base === 16) {
this._parseHex(number, start, endian);
} else {
this._parseBase(number, base, start);
if (endian === 'le') {
this._initArray(this.toArray(), base, endian);
}
}
}
};
BN.prototype._initNumber = function _initNumber (number, base, endian) {
if (number < 0) {
this.negative = 1;
number = -number;
}
if (number < 0x4000000) {
this.words = [ number & 0x3ffffff ];
this.length = 1;
} else if (number < 0x10000000000000) {
this.words = [
number & 0x3ffffff,
(number / 0x4000000) & 0x3ffffff
];
this.length = 2;
} else {
assert(number < 0x20000000000000); // 2 ^ 53 (unsafe)
this.words = [
number & 0x3ffffff,
(number / 0x4000000) & 0x3ffffff,
1
];
this.length = 3;
}
if (endian !== 'le') return;
// Reverse the bytes
this._initArray(this.toArray(), base, endian);
};
BN.prototype._initArray = function _initArray (number, base, endian) {
// Perhaps a Uint8Array
assert(typeof number.length === 'number');
if (number.length <= 0) {
this.words = [ 0 ];
this.length = 1;
return this;
}
this.length = Math.ceil(number.length / 3);
this.words = new Array(this.length);
for (var i = 0; i < this.length; i++) {
this.words[i] = 0;
}
var j, w;
var off = 0;
if (endian === 'be') {
for (i = number.length - 1, j = 0; i >= 0; i -= 3) {
w = number[i] | (number[i - 1] << 8) | (number[i - 2] << 16);
this.words[j] |= (w << off) & 0x3ffffff;
this.words[j + 1] = (w >>> (26 - off)) & 0x3ffffff;
off += 24;
if (off >= 26) {
off -= 26;
j++;
}
}
} else if (endian === 'le') {
for (i = 0, j = 0; i < number.length; i += 3) {
w = number[i] | (number[i + 1] << 8) | (number[i + 2] << 16);
this.words[j] |= (w << off) & 0x3ffffff;
this.words[j + 1] = (w >>> (26 - off)) & 0x3ffffff;
off += 24;
if (off >= 26) {
off -= 26;
j++;
}
}
}
return this.strip();
};
function parseHex4Bits (string, index) {
var c = string.charCodeAt(index);
// 'A' - 'F'
if (c >= 65 && c <= 70) {
return c - 55;
// 'a' - 'f'
} else if (c >= 97 && c <= 102) {
return c - 87;
// '0' - '9'
} else {
return (c - 48) & 0xf;
}
}
function parseHexByte (string, lowerBound, index) {
var r = parseHex4Bits(string, index);
if (index - 1 >= lowerBound) {
r |= parseHex4Bits(string, index - 1) << 4;
}
return r;
}
BN.prototype._parseHex = function _parseHex (number, start, endian) {
// Create possibly bigger array to ensure that it fits the number
this.length = Math.ceil((number.length - start) / 6);
this.words = new Array(this.length);
for (var i = 0; i < this.length; i++) {
this.words[i] = 0;
}
// 24-bits chunks
var off = 0;
var j = 0;
var w;
if (endian === 'be') {
for (i = number.length - 1; i >= start; i -= 2) {
w = parseHexByte(number, start, i) << off;
this.words[j] |= w & 0x3ffffff;
if (off >= 18) {
off -= 18;
j += 1;
this.words[j] |= w >>> 26;
} else {
off += 8;
}
}
} else {
var parseLength = number.length - start;
for (i = parseLength % 2 === 0 ? start + 1 : start; i < number.length; i += 2) {
w = parseHexByte(number, start, i) << off;
this.words[j] |= w & 0x3ffffff;
if (off >= 18) {
off -= 18;
j += 1;
this.words[j] |= w >>> 26;
} else {
off += 8;
}
}
}
this.strip();
};
function parseBase (str, start, end, mul) {
var r = 0;
var len = Math.min(str.length, end);
for (var i = start; i < len; i++) {
var c = str.charCodeAt(i) - 48;
r *= mul;
// 'a'
if (c >= 49) {
r += c - 49 + 0xa;
// 'A'
} else if (c >= 17) {
r += c - 17 + 0xa;
// '0' - '9'
} else {
r += c;
}
}
return r;
}
BN.prototype._parseBase = function _parseBase (number, base, start) {
// Initialize as zero
this.words = [ 0 ];
this.length = 1;
// Find length of limb in base
for (var limbLen = 0, limbPow = 1; limbPow <= 0x3ffffff; limbPow *= base) {
limbLen++;
}
limbLen--;
limbPow = (limbPow / base) | 0;
var total = number.length - start;
var mod = total % limbLen;
var end = Math.min(total, total - mod) + start;
var word = 0;
for (var i = start; i < end; i += limbLen) {
word = parseBase(number, i, i + limbLen, base);
this.imuln(limbPow);
if (this.words[0] + word < 0x4000000) {
this.words[0] += word;
} else {
this._iaddn(word);
}
}
if (mod !== 0) {
var pow = 1;
word = parseBase(number, i, number.length, base);
for (i = 0; i < mod; i++) {
pow *= base;
}
this.imuln(pow);
if (this.words[0] + word < 0x4000000) {
this.words[0] += word;
} else {
this._iaddn(word);
}
}
this.strip();
};
BN.prototype.copy = function copy (dest) {
dest.words = new Array(this.length);
for (var i = 0; i < this.length; i++) {
dest.words[i] = this.words[i];
}
dest.length = this.length;
dest.negative = this.negative;
dest.red = this.red;
};
BN.prototype.clone = function clone () {
var r = new BN(null);
this.copy(r);
return r;
};
BN.prototype._expand = function _expand (size) {
while (this.length < size) {
this.words[this.length++] = 0;
}
return this;
};
// Remove leading `0` from `this`
BN.prototype.strip = function strip () {
while (this.length > 1 && this.words[this.length - 1] === 0) {
this.length--;
}
return this._normSign();
};
BN.prototype._normSign = function _normSign () {
// -0 = 0
if (this.length === 1 && this.words[0] === 0) {
this.negative = 0;
}
return this;
};
BN.prototype.inspect = function inspect () {
return (this.red ? '';
};
/*
var zeros = [];
var groupSizes = [];
var groupBases = [];
var s = '';
var i = -1;
while (++i < BN.wordSize) {
zeros[i] = s;
s += '0';
}
groupSizes[0] = 0;
groupSizes[1] = 0;
groupBases[0] = 0;
groupBases[1] = 0;
var base = 2 - 1;
while (++base < 36 + 1) {
var groupSize = 0;
var groupBase = 1;
while (groupBase < (1 << BN.wordSize) / base) {
groupBase *= base;
groupSize += 1;
}
groupSizes[base] = groupSize;
groupBases[base] = groupBase;
}
*/
var zeros = [
'',
'0',
'00',
'000',
'0000',
'00000',
'000000',
'0000000',
'00000000',
'000000000',
'0000000000',
'00000000000',
'000000000000',
'0000000000000',
'00000000000000',
'000000000000000',
'0000000000000000',
'00000000000000000',
'000000000000000000',
'0000000000000000000',
'00000000000000000000',
'000000000000000000000',
'0000000000000000000000',
'00000000000000000000000',
'000000000000000000000000',
'0000000000000000000000000'
];
var groupSizes = [
0, 0,
25, 16, 12, 11, 10, 9, 8,
8, 7, 7, 7, 7, 6, 6,
6, 6, 6, 6, 6, 5, 5,
5, 5, 5, 5, 5, 5, 5,
5, 5, 5, 5, 5, 5, 5
];
var groupBases = [
0, 0,
33554432, 43046721, 16777216, 48828125, 60466176, 40353607, 16777216,
43046721, 10000000, 19487171, 35831808, 62748517, 7529536, 11390625,
16777216, 24137569, 34012224, 47045881, 64000000, 4084101, 5153632,
6436343, 7962624, 9765625, 11881376, 14348907, 17210368, 20511149,
24300000, 28629151, 33554432, 39135393, 45435424, 52521875, 60466176
];
BN.prototype.toString = function toString (base, padding) {
base = base || 10;
padding = padding | 0 || 1;
var out;
if (base === 16 || base === 'hex') {
out = '';
var off = 0;
var carry = 0;
for (var i = 0; i < this.length; i++) {
var w = this.words[i];
var word = (((w << off) | carry) & 0xffffff).toString(16);
carry = (w >>> (24 - off)) & 0xffffff;
if (carry !== 0 || i !== this.length - 1) {
out = zeros[6 - word.length] + word + out;
} else {
out = word + out;
}
off += 2;
if (off >= 26) {
off -= 26;
i--;
}
}
if (carry !== 0) {
out = carry.toString(16) + out;
}
while (out.length % padding !== 0) {
out = '0' + out;
}
if (this.negative !== 0) {
out = '-' + out;
}
return out;
}
if (base === (base | 0) && base >= 2 && base <= 36) {
// var groupSize = Math.floor(BN.wordSize * Math.LN2 / Math.log(base));
var groupSize = groupSizes[base];
// var groupBase = Math.pow(base, groupSize);
var groupBase = groupBases[base];
out = '';
var c = this.clone();
c.negative = 0;
while (!c.isZero()) {
var r = c.modn(groupBase).toString(base);
c = c.idivn(groupBase);
if (!c.isZero()) {
out = zeros[groupSize - r.length] + r + out;
} else {
out = r + out;
}
}
if (this.isZero()) {
out = '0' + out;
}
while (out.length % padding !== 0) {
out = '0' + out;
}
if (this.negative !== 0) {
out = '-' + out;
}
return out;
}
assert(false, 'Base should be between 2 and 36');
};
BN.prototype.toNumber = function toNumber () {
var ret = this.words[0];
if (this.length === 2) {
ret += this.words[1] * 0x4000000;
} else if (this.length === 3 && this.words[2] === 0x01) {
// NOTE: at this stage it is known that the top bit is set
ret += 0x10000000000000 + (this.words[1] * 0x4000000);
} else if (this.length > 2) {
assert(false, 'Number can only safely store up to 53 bits');
}
return (this.negative !== 0) ? -ret : ret;
};
BN.prototype.toJSON = function toJSON () {
return this.toString(16);
};
BN.prototype.toBuffer = function toBuffer (endian, length) {
assert(typeof Buffer !== 'undefined');
return this.toArrayLike(Buffer, endian, length);
};
BN.prototype.toArray = function toArray (endian, length) {
return this.toArrayLike(Array, endian, length);
};
BN.prototype.toArrayLike = function toArrayLike (ArrayType, endian, length) {
var byteLength = this.byteLength();
var reqLength = length || Math.max(1, byteLength);
assert(byteLength <= reqLength, 'byte array longer than desired length');
assert(reqLength > 0, 'Requested array length <= 0');
this.strip();
var littleEndian = endian === 'le';
var res = new ArrayType(reqLength);
var b, i;
var q = this.clone();
if (!littleEndian) {
// Assume big-endian
for (i = 0; i < reqLength - byteLength; i++) {
res[i] = 0;
}
for (i = 0; !q.isZero(); i++) {
b = q.andln(0xff);
q.iushrn(8);
res[reqLength - i - 1] = b;
}
} else {
for (i = 0; !q.isZero(); i++) {
b = q.andln(0xff);
q.iushrn(8);
res[i] = b;
}
for (; i < reqLength; i++) {
res[i] = 0;
}
}
return res;
};
if (Math.clz32) {
BN.prototype._countBits = function _countBits (w) {
return 32 - Math.clz32(w);
};
} else {
BN.prototype._countBits = function _countBits (w) {
var t = w;
var r = 0;
if (t >= 0x1000) {
r += 13;
t >>>= 13;
}
if (t >= 0x40) {
r += 7;
t >>>= 7;
}
if (t >= 0x8) {
r += 4;
t >>>= 4;
}
if (t >= 0x02) {
r += 2;
t >>>= 2;
}
return r + t;
};
}
BN.prototype._zeroBits = function _zeroBits (w) {
// Short-cut
if (w === 0) return 26;
var t = w;
var r = 0;
if ((t & 0x1fff) === 0) {
r += 13;
t >>>= 13;
}
if ((t & 0x7f) === 0) {
r += 7;
t >>>= 7;
}
if ((t & 0xf) === 0) {
r += 4;
t >>>= 4;
}
if ((t & 0x3) === 0) {
r += 2;
t >>>= 2;
}
if ((t & 0x1) === 0) {
r++;
}
return r;
};
// Return number of used bits in a BN
BN.prototype.bitLength = function bitLength () {
var w = this.words[this.length - 1];
var hi = this._countBits(w);
return (this.length - 1) * 26 + hi;
};
function toBitArray (num) {
var w = new Array(num.bitLength());
for (var bit = 0; bit < w.length; bit++) {
var off = (bit / 26) | 0;
var wbit = bit % 26;
w[bit] = (num.words[off] & (1 << wbit)) >>> wbit;
}
return w;
}
// Number of trailing zero bits
BN.prototype.zeroBits = function zeroBits () {
if (this.isZero()) return 0;
var r = 0;
for (var i = 0; i < this.length; i++) {
var b = this._zeroBits(this.words[i]);
r += b;
if (b !== 26) break;
}
return r;
};
BN.prototype.byteLength = function byteLength () {
return Math.ceil(this.bitLength() / 8);
};
BN.prototype.toTwos = function toTwos (width) {
if (this.negative !== 0) {
return this.abs().inotn(width).iaddn(1);
}
return this.clone();
};
BN.prototype.fromTwos = function fromTwos (width) {
if (this.testn(width - 1)) {
return this.notn(width).iaddn(1).ineg();
}
return this.clone();
};
BN.prototype.isNeg = function isNeg () {
return this.negative !== 0;
};
// Return negative clone of `this`
BN.prototype.neg = function neg () {
return this.clone().ineg();
};
BN.prototype.ineg = function ineg () {
if (!this.isZero()) {
this.negative ^= 1;
}
return this;
};
// Or `num` with `this` in-place
BN.prototype.iuor = function iuor (num) {
while (this.length < num.length) {
this.words[this.length++] = 0;
}
for (var i = 0; i < num.length; i++) {
this.words[i] = this.words[i] | num.words[i];
}
return this.strip();
};
BN.prototype.ior = function ior (num) {
assert((this.negative | num.negative) === 0);
return this.iuor(num);
};
// Or `num` with `this`
BN.prototype.or = function or (num) {
if (this.length > num.length) return this.clone().ior(num);
return num.clone().ior(this);
};
BN.prototype.uor = function uor (num) {
if (this.length > num.length) return this.clone().iuor(num);
return num.clone().iuor(this);
};
// And `num` with `this` in-place
BN.prototype.iuand = function iuand (num) {
// b = min-length(num, this)
var b;
if (this.length > num.length) {
b = num;
} else {
b = this;
}
for (var i = 0; i < b.length; i++) {
this.words[i] = this.words[i] & num.words[i];
}
this.length = b.length;
return this.strip();
};
BN.prototype.iand = function iand (num) {
assert((this.negative | num.negative) === 0);
return this.iuand(num);
};
// And `num` with `this`
BN.prototype.and = function and (num) {
if (this.length > num.length) return this.clone().iand(num);
return num.clone().iand(this);
};
BN.prototype.uand = function uand (num) {
if (this.length > num.length) return this.clone().iuand(num);
return num.clone().iuand(this);
};
// Xor `num` with `this` in-place
BN.prototype.iuxor = function iuxor (num) {
// a.length > b.length
var a;
var b;
if (this.length > num.length) {
a = this;
b = num;
} else {
a = num;
b = this;
}
for (var i = 0; i < b.length; i++) {
this.words[i] = a.words[i] ^ b.words[i];
}
if (this !== a) {
for (; i < a.length; i++) {
this.words[i] = a.words[i];
}
}
this.length = a.length;
return this.strip();
};
BN.prototype.ixor = function ixor (num) {
assert((this.negative | num.negative) === 0);
return this.iuxor(num);
};
// Xor `num` with `this`
BN.prototype.xor = function xor (num) {
if (this.length > num.length) return this.clone().ixor(num);
return num.clone().ixor(this);
};
BN.prototype.uxor = function uxor (num) {
if (this.length > num.length) return this.clone().iuxor(num);
return num.clone().iuxor(this);
};
// Not ``this`` with ``width`` bitwidth
BN.prototype.inotn = function inotn (width) {
assert(typeof width === 'number' && width >= 0);
var bytesNeeded = Math.ceil(width / 26) | 0;
var bitsLeft = width % 26;
// Extend the buffer with leading zeroes
this._expand(bytesNeeded);
if (bitsLeft > 0) {
bytesNeeded--;
}
// Handle complete words
for (var i = 0; i < bytesNeeded; i++) {
this.words[i] = ~this.words[i] & 0x3ffffff;
}
// Handle the residue
if (bitsLeft > 0) {
this.words[i] = ~this.words[i] & (0x3ffffff >> (26 - bitsLeft));
}
// And remove leading zeroes
return this.strip();
};
BN.prototype.notn = function notn (width) {
return this.clone().inotn(width);
};
// Set `bit` of `this`
BN.prototype.setn = function setn (bit, val) {
assert(typeof bit === 'number' && bit >= 0);
var off = (bit / 26) | 0;
var wbit = bit % 26;
this._expand(off + 1);
if (val) {
this.words[off] = this.words[off] | (1 << wbit);
} else {
this.words[off] = this.words[off] & ~(1 << wbit);
}
return this.strip();
};
// Add `num` to `this` in-place
BN.prototype.iadd = function iadd (num) {
var r;
// negative + positive
if (this.negative !== 0 && num.negative === 0) {
this.negative = 0;
r = this.isub(num);
this.negative ^= 1;
return this._normSign();
// positive + negative
} else if (this.negative === 0 && num.negative !== 0) {
num.negative = 0;
r = this.isub(num);
num.negative = 1;
return r._normSign();
}
// a.length > b.length
var a, b;
if (this.length > num.length) {
a = this;
b = num;
} else {
a = num;
b = this;
}
var carry = 0;
for (var i = 0; i < b.length; i++) {
r = (a.words[i] | 0) + (b.words[i] | 0) + carry;
this.words[i] = r & 0x3ffffff;
carry = r >>> 26;
}
for (; carry !== 0 && i < a.length; i++) {
r = (a.words[i] | 0) + carry;
this.words[i] = r & 0x3ffffff;
carry = r >>> 26;
}
this.length = a.length;
if (carry !== 0) {
this.words[this.length] = carry;
this.length++;
// Copy the rest of the words
} else if (a !== this) {
for (; i < a.length; i++) {
this.words[i] = a.words[i];
}
}
return this;
};
// Add `num` to `this`
BN.prototype.add = function add (num) {
var res;
if (num.negative !== 0 && this.negative === 0) {
num.negative = 0;
res = this.sub(num);
num.negative ^= 1;
return res;
} else if (num.negative === 0 && this.negative !== 0) {
this.negative = 0;
res = num.sub(this);
this.negative = 1;
return res;
}
if (this.length > num.length) return this.clone().iadd(num);
return num.clone().iadd(this);
};
// Subtract `num` from `this` in-place
BN.prototype.isub = function isub (num) {
// this - (-num) = this + num
if (num.negative !== 0) {
num.negative = 0;
var r = this.iadd(num);
num.negative = 1;
return r._normSign();
// -this - num = -(this + num)
} else if (this.negative !== 0) {
this.negative = 0;
this.iadd(num);
this.negative = 1;
return this._normSign();
}
// At this point both numbers are positive
var cmp = this.cmp(num);
// Optimization - zeroify
if (cmp === 0) {
this.negative = 0;
this.length = 1;
this.words[0] = 0;
return this;
}
// a > b
var a, b;
if (cmp > 0) {
a = this;
b = num;
} else {
a = num;
b = this;
}
var carry = 0;
for (var i = 0; i < b.length; i++) {
r = (a.words[i] | 0) - (b.words[i] | 0) + carry;
carry = r >> 26;
this.words[i] = r & 0x3ffffff;
}
for (; carry !== 0 && i < a.length; i++) {
r = (a.words[i] | 0) + carry;
carry = r >> 26;
this.words[i] = r & 0x3ffffff;
}
// Copy rest of the words
if (carry === 0 && i < a.length && a !== this) {
for (; i < a.length; i++) {
this.words[i] = a.words[i];
}
}
this.length = Math.max(this.length, i);
if (a !== this) {
this.negative = 1;
}
return this.strip();
};
// Subtract `num` from `this`
BN.prototype.sub = function sub (num) {
return this.clone().isub(num);
};
function smallMulTo (self, num, out) {
out.negative = num.negative ^ self.negative;
var len = (self.length + num.length) | 0;
out.length = len;
len = (len - 1) | 0;
// Peel one iteration (compiler can't do it, because of code complexity)
var a = self.words[0] | 0;
var b = num.words[0] | 0;
var r = a * b;
var lo = r & 0x3ffffff;
var carry = (r / 0x4000000) | 0;
out.words[0] = lo;
for (var k = 1; k < len; k++) {
// Sum all words with the same `i + j = k` and accumulate `ncarry`,
// note that ncarry could be >= 0x3ffffff
var ncarry = carry >>> 26;
var rword = carry & 0x3ffffff;
var maxJ = Math.min(k, num.length - 1);
for (var j = Math.max(0, k - self.length + 1); j <= maxJ; j++) {
var i = (k - j) | 0;
a = self.words[i] | 0;
b = num.words[j] | 0;
r = a * b + rword;
ncarry += (r / 0x4000000) | 0;
rword = r & 0x3ffffff;
}
out.words[k] = rword | 0;
carry = ncarry | 0;
}
if (carry !== 0) {
out.words[k] = carry | 0;
} else {
out.length--;
}
return out.strip();
}
// TODO(indutny): it may be reasonable to omit it for users who don't need
// to work with 256-bit numbers, otherwise it gives 20% improvement for 256-bit
// multiplication (like elliptic secp256k1).
var comb10MulTo = function comb10MulTo (self, num, out) {
var a = self.words;
var b = num.words;
var o = out.words;
var c = 0;
var lo;
var mid;
var hi;
var a0 = a[0] | 0;
var al0 = a0 & 0x1fff;
var ah0 = a0 >>> 13;
var a1 = a[1] | 0;
var al1 = a1 & 0x1fff;
var ah1 = a1 >>> 13;
var a2 = a[2] | 0;
var al2 = a2 & 0x1fff;
var ah2 = a2 >>> 13;
var a3 = a[3] | 0;
var al3 = a3 & 0x1fff;
var ah3 = a3 >>> 13;
var a4 = a[4] | 0;
var al4 = a4 & 0x1fff;
var ah4 = a4 >>> 13;
var a5 = a[5] | 0;
var al5 = a5 & 0x1fff;
var ah5 = a5 >>> 13;
var a6 = a[6] | 0;
var al6 = a6 & 0x1fff;
var ah6 = a6 >>> 13;
var a7 = a[7] | 0;
var al7 = a7 & 0x1fff;
var ah7 = a7 >>> 13;
var a8 = a[8] | 0;
var al8 = a8 & 0x1fff;
var ah8 = a8 >>> 13;
var a9 = a[9] | 0;
var al9 = a9 & 0x1fff;
var ah9 = a9 >>> 13;
var b0 = b[0] | 0;
var bl0 = b0 & 0x1fff;
var bh0 = b0 >>> 13;
var b1 = b[1] | 0;
var bl1 = b1 & 0x1fff;
var bh1 = b1 >>> 13;
var b2 = b[2] | 0;
var bl2 = b2 & 0x1fff;
var bh2 = b2 >>> 13;
var b3 = b[3] | 0;
var bl3 = b3 & 0x1fff;
var bh3 = b3 >>> 13;
var b4 = b[4] | 0;
var bl4 = b4 & 0x1fff;
var bh4 = b4 >>> 13;
var b5 = b[5] | 0;
var bl5 = b5 & 0x1fff;
var bh5 = b5 >>> 13;
var b6 = b[6] | 0;
var bl6 = b6 & 0x1fff;
var bh6 = b6 >>> 13;
var b7 = b[7] | 0;
var bl7 = b7 & 0x1fff;
var bh7 = b7 >>> 13;
var b8 = b[8] | 0;
var bl8 = b8 & 0x1fff;
var bh8 = b8 >>> 13;
var b9 = b[9] | 0;
var bl9 = b9 & 0x1fff;
var bh9 = b9 >>> 13;
out.negative = self.negative ^ num.negative;
out.length = 19;
/* k = 0 */
lo = Math.imul(al0, bl0);
mid = Math.imul(al0, bh0);
mid = (mid + Math.imul(ah0, bl0)) | 0;
hi = Math.imul(ah0, bh0);
var w0 = (((c + lo) | 0) + ((mid & 0x1fff) << 13)) | 0;
c = (((hi + (mid >>> 13)) | 0) + (w0 >>> 26)) | 0;
w0 &= 0x3ffffff;
/* k = 1 */
lo = Math.imul(al1, bl0);
mid = Math.imul(al1, bh0);
mid = (mid + Math.imul(ah1, bl0)) | 0;
hi = Math.imul(ah1, bh0);
lo = (lo + Math.imul(al0, bl1)) | 0;
mid = (mid + Math.imul(al0, bh1)) | 0;
mid = (mid + Math.imul(ah0, bl1)) | 0;
hi = (hi + Math.imul(ah0, bh1)) | 0;
var w1 = (((c + lo) | 0) + ((mid & 0x1fff) << 13)) | 0;
c = (((hi + (mid >>> 13)) | 0) + (w1 >>> 26)) | 0;
w1 &= 0x3ffffff;
/* k = 2 */
lo = Math.imul(al2, bl0);
mid = Math.imul(al2, bh0);
mid = (mid + Math.imul(ah2, bl0)) | 0;
hi = Math.imul(ah2, bh0);
lo = (lo + Math.imul(al1, bl1)) | 0;
mid = (mid + Math.imul(al1, bh1)) | 0;
mid = (mid + Math.imul(ah1, bl1)) | 0;
hi = (hi + Math.imul(ah1, bh1)) | 0;
lo = (lo + Math.imul(al0, bl2)) | 0;
mid = (mid + Math.imul(al0, bh2)) | 0;
mid = (mid + Math.imul(ah0, bl2)) | 0;
hi = (hi + Math.imul(ah0, bh2)) | 0;
var w2 = (((c + lo) | 0) + ((mid & 0x1fff) << 13)) | 0;
c = (((hi + (mid >>> 13)) | 0) + (w2 >>> 26)) | 0;
w2 &= 0x3ffffff;
/* k = 3 */
lo = Math.imul(al3, bl0);
mid = Math.imul(al3, bh0);
mid = (mid + Math.imul(ah3, bl0)) | 0;
hi = Math.imul(ah3, bh0);
lo = (lo + Math.imul(al2, bl1)) | 0;
mid = (mid + Math.imul(al2, bh1)) | 0;
mid = (mid + Math.imul(ah2, bl1)) | 0;
hi = (hi + Math.imul(ah2, bh1)) | 0;
lo = (lo + Math.imul(al1, bl2)) | 0;
mid = (mid + Math.imul(al1, bh2)) | 0;
mid = (mid + Math.imul(ah1, bl2)) | 0;
hi = (hi + Math.imul(ah1, bh2)) | 0;
lo = (lo + Math.imul(al0, bl3)) | 0;
mid = (mid + Math.imul(al0, bh3)) | 0;
mid = (mid + Math.imul(ah0, bl3)) | 0;
hi = (hi + Math.imul(ah0, bh3)) | 0;
var w3 = (((c + lo) | 0) + ((mid & 0x1fff) << 13)) | 0;
c = (((hi + (mid >>> 13)) | 0) + (w3 >>> 26)) | 0;
w3 &= 0x3ffffff;
/* k = 4 */
lo = Math.imul(al4, bl0);
mid = Math.imul(al4, bh0);
mid = (mid + Math.imul(ah4, bl0)) | 0;
hi = Math.imul(ah4, bh0);
lo = (lo + Math.imul(al3, bl1)) | 0;
mid = (mid + Math.imul(al3, bh1)) | 0;
mid = (mid + Math.imul(ah3, bl1)) | 0;
hi = (hi + Math.imul(ah3, bh1)) | 0;
lo = (lo + Math.imul(al2, bl2)) | 0;
mid = (mid + Math.imul(al2, bh2)) | 0;
mid = (mid + Math.imul(ah2, bl2)) | 0;
hi = (hi + Math.imul(ah2, bh2)) | 0;
lo = (lo + Math.imul(al1, bl3)) | 0;
mid = (mid + Math.imul(al1, bh3)) | 0;
mid = (mid + Math.imul(ah1, bl3)) | 0;
hi = (hi + Math.imul(ah1, bh3)) | 0;
lo = (lo + Math.imul(al0, bl4)) | 0;
mid = (mid + Math.imul(al0, bh4)) | 0;
mid = (mid + Math.imul(ah0, bl4)) | 0;
hi = (hi + Math.imul(ah0, bh4)) | 0;
var w4 = (((c + lo) | 0) + ((mid & 0x1fff) << 13)) | 0;
c = (((hi + (mid >>> 13)) | 0) + (w4 >>> 26)) | 0;
w4 &= 0x3ffffff;
/* k = 5 */
lo = Math.imul(al5, bl0);
mid = Math.imul(al5, bh0);
mid = (mid + Math.imul(ah5, bl0)) | 0;
hi = Math.imul(ah5, bh0);
lo = (lo + Math.imul(al4, bl1)) | 0;
mid = (mid + Math.imul(al4, bh1)) | 0;
mid = (mid + Math.imul(ah4, bl1)) | 0;
hi = (hi + Math.imul(ah4, bh1)) | 0;
lo = (lo + Math.imul(al3, bl2)) | 0;
mid = (mid + Math.imul(al3, bh2)) | 0;
mid = (mid + Math.imul(ah3, bl2)) | 0;
hi = (hi + Math.imul(ah3, bh2)) | 0;
lo = (lo + Math.imul(al2, bl3)) | 0;
mid = (mid + Math.imul(al2, bh3)) | 0;
mid = (mid + Math.imul(ah2, bl3)) | 0;
hi = (hi + Math.imul(ah2, bh3)) | 0;
lo = (lo + Math.imul(al1, bl4)) | 0;
mid = (mid + Math.imul(al1, bh4)) | 0;
mid = (mid + Math.imul(ah1, bl4)) | 0;
hi = (hi + Math.imul(ah1, bh4)) | 0;
lo = (lo + Math.imul(al0, bl5)) | 0;
mid = (mid + Math.imul(al0, bh5)) | 0;
mid = (mid + Math.imul(ah0, bl5)) | 0;
hi = (hi + Math.imul(ah0, bh5)) | 0;
var w5 = (((c + lo) | 0) + ((mid & 0x1fff) << 13)) | 0;
c = (((hi + (mid >>> 13)) | 0) + (w5 >>> 26)) | 0;
w5 &= 0x3ffffff;
/* k = 6 */
lo = Math.imul(al6, bl0);
mid = Math.imul(al6, bh0);
mid = (mid + Math.imul(ah6, bl0)) | 0;
hi = Math.imul(ah6, bh0);
lo = (lo + Math.imul(al5, bl1)) | 0;
mid = (mid + Math.imul(al5, bh1)) | 0;
mid = (mid + Math.imul(ah5, bl1)) | 0;
hi = (hi + Math.imul(ah5, bh1)) | 0;
lo = (lo + Math.imul(al4, bl2)) | 0;
mid = (mid + Math.imul(al4, bh2)) | 0;
mid = (mid + Math.imul(ah4, bl2)) | 0;
hi = (hi + Math.imul(ah4, bh2)) | 0;
lo = (lo + Math.imul(al3, bl3)) | 0;
mid = (mid + Math.imul(al3, bh3)) | 0;
mid = (mid + Math.imul(ah3, bl3)) | 0;
hi = (hi + Math.imul(ah3, bh3)) | 0;
lo = (lo + Math.imul(al2, bl4)) | 0;
mid = (mid + Math.imul(al2, bh4)) | 0;
mid = (mid + Math.imul(ah2, bl4)) | 0;
hi = (hi + Math.imul(ah2, bh4)) | 0;
lo = (lo + Math.imul(al1, bl5)) | 0;
mid = (mid + Math.imul(al1, bh5)) | 0;
mid = (mid + Math.imul(ah1, bl5)) | 0;
hi = (hi + Math.imul(ah1, bh5)) | 0;
lo = (lo + Math.imul(al0, bl6)) | 0;
mid = (mid + Math.imul(al0, bh6)) | 0;
mid = (mid + Math.imul(ah0, bl6)) | 0;
hi = (hi + Math.imul(ah0, bh6)) | 0;
var w6 = (((c + lo) | 0) + ((mid & 0x1fff) << 13)) | 0;
c = (((hi + (mid >>> 13)) | 0) + (w6 >>> 26)) | 0;
w6 &= 0x3ffffff;
/* k = 7 */
lo = Math.imul(al7, bl0);
mid = Math.imul(al7, bh0);
mid = (mid + Math.imul(ah7, bl0)) | 0;
hi = Math.imul(ah7, bh0);
lo = (lo + Math.imul(al6, bl1)) | 0;
mid = (mid + Math.imul(al6, bh1)) | 0;
mid = (mid + Math.imul(ah6, bl1)) | 0;
hi = (hi + Math.imul(ah6, bh1)) | 0;
lo = (lo + Math.imul(al5, bl2)) | 0;
mid = (mid + Math.imul(al5, bh2)) | 0;
mid = (mid + Math.imul(ah5, bl2)) | 0;
hi = (hi + Math.imul(ah5, bh2)) | 0;
lo = (lo + Math.imul(al4, bl3)) | 0;
mid = (mid + Math.imul(al4, bh3)) | 0;
mid = (mid + Math.imul(ah4, bl3)) | 0;
hi = (hi + Math.imul(ah4, bh3)) | 0;
lo = (lo + Math.imul(al3, bl4)) | 0;
mid = (mid + Math.imul(al3, bh4)) | 0;
mid = (mid + Math.imul(ah3, bl4)) | 0;
hi = (hi + Math.imul(ah3, bh4)) | 0;
lo = (lo + Math.imul(al2, bl5)) | 0;
mid = (mid + Math.imul(al2, bh5)) | 0;
mid = (mid + Math.imul(ah2, bl5)) | 0;
hi = (hi + Math.imul(ah2, bh5)) | 0;
lo = (lo + Math.imul(al1, bl6)) | 0;
mid = (mid + Math.imul(al1, bh6)) | 0;
mid = (mid + Math.imul(ah1, bl6)) | 0;
hi = (hi + Math.imul(ah1, bh6)) | 0;
lo = (lo + Math.imul(al0, bl7)) | 0;
mid = (mid + Math.imul(al0, bh7)) | 0;
mid = (mid + Math.imul(ah0, bl7)) | 0;
hi = (hi + Math.imul(ah0, bh7)) | 0;
var w7 = (((c + lo) | 0) + ((mid & 0x1fff) << 13)) | 0;
c = (((hi + (mid >>> 13)) | 0) + (w7 >>> 26)) | 0;
w7 &= 0x3ffffff;
/* k = 8 */
lo = Math.imul(al8, bl0);
mid = Math.imul(al8, bh0);
mid = (mid + Math.imul(ah8, bl0)) | 0;
hi = Math.imul(ah8, bh0);
lo = (lo + Math.imul(al7, bl1)) | 0;
mid = (mid + Math.imul(al7, bh1)) | 0;
mid = (mid + Math.imul(ah7, bl1)) | 0;
hi = (hi + Math.imul(ah7, bh1)) | 0;
lo = (lo + Math.imul(al6, bl2)) | 0;
mid = (mid + Math.imul(al6, bh2)) | 0;
mid = (mid + Math.imul(ah6, bl2)) | 0;
hi = (hi + Math.imul(ah6, bh2)) | 0;
lo = (lo + Math.imul(al5, bl3)) | 0;
mid = (mid + Math.imul(al5, bh3)) | 0;
mid = (mid + Math.imul(ah5, bl3)) | 0;
hi = (hi + Math.imul(ah5, bh3)) | 0;
lo = (lo + Math.imul(al4, bl4)) | 0;
mid = (mid + Math.imul(al4, bh4)) | 0;
mid = (mid + Math.imul(ah4, bl4)) | 0;
hi = (hi + Math.imul(ah4, bh4)) | 0;
lo = (lo + Math.imul(al3, bl5)) | 0;
mid = (mid + Math.imul(al3, bh5)) | 0;
mid = (mid + Math.imul(ah3, bl5)) | 0;
hi = (hi + Math.imul(ah3, bh5)) | 0;
lo = (lo + Math.imul(al2, bl6)) | 0;
mid = (mid + Math.imul(al2, bh6)) | 0;
mid = (mid + Math.imul(ah2, bl6)) | 0;
hi = (hi + Math.imul(ah2, bh6)) | 0;
lo = (lo + Math.imul(al1, bl7)) | 0;
mid = (mid + Math.imul(al1, bh7)) | 0;
mid = (mid + Math.imul(ah1, bl7)) | 0;
hi = (hi + Math.imul(ah1, bh7)) | 0;
lo = (lo + Math.imul(al0, bl8)) | 0;
mid = (mid + Math.imul(al0, bh8)) | 0;
mid = (mid + Math.imul(ah0, bl8)) | 0;
hi = (hi + Math.imul(ah0, bh8)) | 0;
var w8 = (((c + lo) | 0) + ((mid & 0x1fff) << 13)) | 0;
c = (((hi + (mid >>> 13)) | 0) + (w8 >>> 26)) | 0;
w8 &= 0x3ffffff;
/* k = 9 */
lo = Math.imul(al9, bl0);
mid = Math.imul(al9, bh0);
mid = (mid + Math.imul(ah9, bl0)) | 0;
hi = Math.imul(ah9, bh0);
lo = (lo + Math.imul(al8, bl1)) | 0;
mid = (mid + Math.imul(al8, bh1)) | 0;
mid = (mid + Math.imul(ah8, bl1)) | 0;
hi = (hi + Math.imul(ah8, bh1)) | 0;
lo = (lo + Math.imul(al7, bl2)) | 0;
mid = (mid + Math.imul(al7, bh2)) | 0;
mid = (mid + Math.imul(ah7, bl2)) | 0;
hi = (hi + Math.imul(ah7, bh2)) | 0;
lo = (lo + Math.imul(al6, bl3)) | 0;
mid = (mid + Math.imul(al6, bh3)) | 0;
mid = (mid + Math.imul(ah6, bl3)) | 0;
hi = (hi + Math.imul(ah6, bh3)) | 0;
lo = (lo + Math.imul(al5, bl4)) | 0;
mid = (mid + Math.imul(al5, bh4)) | 0;
mid = (mid + Math.imul(ah5, bl4)) | 0;
hi = (hi + Math.imul(ah5, bh4)) | 0;
lo = (lo + Math.imul(al4, bl5)) | 0;
mid = (mid + Math.imul(al4, bh5)) | 0;
mid = (mid + Math.imul(ah4, bl5)) | 0;
hi = (hi + Math.imul(ah4, bh5)) | 0;
lo = (lo + Math.imul(al3, bl6)) | 0;
mid = (mid + Math.imul(al3, bh6)) | 0;
mid = (mid + Math.imul(ah3, bl6)) | 0;
hi = (hi + Math.imul(ah3, bh6)) | 0;
lo = (lo + Math.imul(al2, bl7)) | 0;
mid = (mid + Math.imul(al2, bh7)) | 0;
mid = (mid + Math.imul(ah2, bl7)) | 0;
hi = (hi + Math.imul(ah2, bh7)) | 0;
lo = (lo + Math.imul(al1, bl8)) | 0;
mid = (mid + Math.imul(al1, bh8)) | 0;
mid = (mid + Math.imul(ah1, bl8)) | 0;
hi = (hi + Math.imul(ah1, bh8)) | 0;
lo = (lo + Math.imul(al0, bl9)) | 0;
mid = (mid + Math.imul(al0, bh9)) | 0;
mid = (mid + Math.imul(ah0, bl9)) | 0;
hi = (hi + Math.imul(ah0, bh9)) | 0;
var w9 = (((c + lo) | 0) + ((mid & 0x1fff) << 13)) | 0;
c = (((hi + (mid >>> 13)) | 0) + (w9 >>> 26)) | 0;
w9 &= 0x3ffffff;
/* k = 10 */
lo = Math.imul(al9, bl1);
mid = Math.imul(al9, bh1);
mid = (mid + Math.imul(ah9, bl1)) | 0;
hi = Math.imul(ah9, bh1);
lo = (lo + Math.imul(al8, bl2)) | 0;
mid = (mid + Math.imul(al8, bh2)) | 0;
mid = (mid + Math.imul(ah8, bl2)) | 0;
hi = (hi + Math.imul(ah8, bh2)) | 0;
lo = (lo + Math.imul(al7, bl3)) | 0;
mid = (mid + Math.imul(al7, bh3)) | 0;
mid = (mid + Math.imul(ah7, bl3)) | 0;
hi = (hi + Math.imul(ah7, bh3)) | 0;
lo = (lo + Math.imul(al6, bl4)) | 0;
mid = (mid + Math.imul(al6, bh4)) | 0;
mid = (mid + Math.imul(ah6, bl4)) | 0;
hi = (hi + Math.imul(ah6, bh4)) | 0;
lo = (lo + Math.imul(al5, bl5)) | 0;
mid = (mid + Math.imul(al5, bh5)) | 0;
mid = (mid + Math.imul(ah5, bl5)) | 0;
hi = (hi + Math.imul(ah5, bh5)) | 0;
lo = (lo + Math.imul(al4, bl6)) | 0;
mid = (mid + Math.imul(al4, bh6)) | 0;
mid = (mid + Math.imul(ah4, bl6)) | 0;
hi = (hi + Math.imul(ah4, bh6)) | 0;
lo = (lo + Math.imul(al3, bl7)) | 0;
mid = (mid + Math.imul(al3, bh7)) | 0;
mid = (mid + Math.imul(ah3, bl7)) | 0;
hi = (hi + Math.imul(ah3, bh7)) | 0;
lo = (lo + Math.imul(al2, bl8)) | 0;
mid = (mid + Math.imul(al2, bh8)) | 0;
mid = (mid + Math.imul(ah2, bl8)) | 0;
hi = (hi + Math.imul(ah2, bh8)) | 0;
lo = (lo + Math.imul(al1, bl9)) | 0;
mid = (mid + Math.imul(al1, bh9)) | 0;
mid = (mid + Math.imul(ah1, bl9)) | 0;
hi = (hi + Math.imul(ah1, bh9)) | 0;
var w10 = (((c + lo) | 0) + ((mid & 0x1fff) << 13)) | 0;
c = (((hi + (mid >>> 13)) | 0) + (w10 >>> 26)) | 0;
w10 &= 0x3ffffff;
/* k = 11 */
lo = Math.imul(al9, bl2);
mid = Math.imul(al9, bh2);
mid = (mid + Math.imul(ah9, bl2)) | 0;
hi = Math.imul(ah9, bh2);
lo = (lo + Math.imul(al8, bl3)) | 0;
mid = (mid + Math.imul(al8, bh3)) | 0;
mid = (mid + Math.imul(ah8, bl3)) | 0;
hi = (hi + Math.imul(ah8, bh3)) | 0;
lo = (lo + Math.imul(al7, bl4)) | 0;
mid = (mid + Math.imul(al7, bh4)) | 0;
mid = (mid + Math.imul(ah7, bl4)) | 0;
hi = (hi + Math.imul(ah7, bh4)) | 0;
lo = (lo + Math.imul(al6, bl5)) | 0;
mid = (mid + Math.imul(al6, bh5)) | 0;
mid = (mid + Math.imul(ah6, bl5)) | 0;
hi = (hi + Math.imul(ah6, bh5)) | 0;
lo = (lo + Math.imul(al5, bl6)) | 0;
mid = (mid + Math.imul(al5, bh6)) | 0;
mid = (mid + Math.imul(ah5, bl6)) | 0;
hi = (hi + Math.imul(ah5, bh6)) | 0;
lo = (lo + Math.imul(al4, bl7)) | 0;
mid = (mid + Math.imul(al4, bh7)) | 0;
mid = (mid + Math.imul(ah4, bl7)) | 0;
hi = (hi + Math.imul(ah4, bh7)) | 0;
lo = (lo + Math.imul(al3, bl8)) | 0;
mid = (mid + Math.imul(al3, bh8)) | 0;
mid = (mid + Math.imul(ah3, bl8)) | 0;
hi = (hi + Math.imul(ah3, bh8)) | 0;
lo = (lo + Math.imul(al2, bl9)) | 0;
mid = (mid + Math.imul(al2, bh9)) | 0;
mid = (mid + Math.imul(ah2, bl9)) | 0;
hi = (hi + Math.imul(ah2, bh9)) | 0;
var w11 = (((c + lo) | 0) + ((mid & 0x1fff) << 13)) | 0;
c = (((hi + (mid >>> 13)) | 0) + (w11 >>> 26)) | 0;
w11 &= 0x3ffffff;
/* k = 12 */
lo = Math.imul(al9, bl3);
mid = Math.imul(al9, bh3);
mid = (mid + Math.imul(ah9, bl3)) | 0;
hi = Math.imul(ah9, bh3);
lo = (lo + Math.imul(al8, bl4)) | 0;
mid = (mid + Math.imul(al8, bh4)) | 0;
mid = (mid + Math.imul(ah8, bl4)) | 0;
hi = (hi + Math.imul(ah8, bh4)) | 0;
lo = (lo + Math.imul(al7, bl5)) | 0;
mid = (mid + Math.imul(al7, bh5)) | 0;
mid = (mid + Math.imul(ah7, bl5)) | 0;
hi = (hi + Math.imul(ah7, bh5)) | 0;
lo = (lo + Math.imul(al6, bl6)) | 0;
mid = (mid + Math.imul(al6, bh6)) | 0;
mid = (mid + Math.imul(ah6, bl6)) | 0;
hi = (hi + Math.imul(ah6, bh6)) | 0;
lo = (lo + Math.imul(al5, bl7)) | 0;
mid = (mid + Math.imul(al5, bh7)) | 0;
mid = (mid + Math.imul(ah5, bl7)) | 0;
hi = (hi + Math.imul(ah5, bh7)) | 0;
lo = (lo + Math.imul(al4, bl8)) | 0;
mid = (mid + Math.imul(al4, bh8)) | 0;
mid = (mid + Math.imul(ah4, bl8)) | 0;
hi = (hi + Math.imul(ah4, bh8)) | 0;
lo = (lo + Math.imul(al3, bl9)) | 0;
mid = (mid + Math.imul(al3, bh9)) | 0;
mid = (mid + Math.imul(ah3, bl9)) | 0;
hi = (hi + Math.imul(ah3, bh9)) | 0;
var w12 = (((c + lo) | 0) + ((mid & 0x1fff) << 13)) | 0;
c = (((hi + (mid >>> 13)) | 0) + (w12 >>> 26)) | 0;
w12 &= 0x3ffffff;
/* k = 13 */
lo = Math.imul(al9, bl4);
mid = Math.imul(al9, bh4);
mid = (mid + Math.imul(ah9, bl4)) | 0;
hi = Math.imul(ah9, bh4);
lo = (lo + Math.imul(al8, bl5)) | 0;
mid = (mid + Math.imul(al8, bh5)) | 0;
mid = (mid + Math.imul(ah8, bl5)) | 0;
hi = (hi + Math.imul(ah8, bh5)) | 0;
lo = (lo + Math.imul(al7, bl6)) | 0;
mid = (mid + Math.imul(al7, bh6)) | 0;
mid = (mid + Math.imul(ah7, bl6)) | 0;
hi = (hi + Math.imul(ah7, bh6)) | 0;
lo = (lo + Math.imul(al6, bl7)) | 0;
mid = (mid + Math.imul(al6, bh7)) | 0;
mid = (mid + Math.imul(ah6, bl7)) | 0;
hi = (hi + Math.imul(ah6, bh7)) | 0;
lo = (lo + Math.imul(al5, bl8)) | 0;
mid = (mid + Math.imul(al5, bh8)) | 0;
mid = (mid + Math.imul(ah5, bl8)) | 0;
hi = (hi + Math.imul(ah5, bh8)) | 0;
lo = (lo + Math.imul(al4, bl9)) | 0;
mid = (mid + Math.imul(al4, bh9)) | 0;
mid = (mid + Math.imul(ah4, bl9)) | 0;
hi = (hi + Math.imul(ah4, bh9)) | 0;
var w13 = (((c + lo) | 0) + ((mid & 0x1fff) << 13)) | 0;
c = (((hi + (mid >>> 13)) | 0) + (w13 >>> 26)) | 0;
w13 &= 0x3ffffff;
/* k = 14 */
lo = Math.imul(al9, bl5);
mid = Math.imul(al9, bh5);
mid = (mid + Math.imul(ah9, bl5)) | 0;
hi = Math.imul(ah9, bh5);
lo = (lo + Math.imul(al8, bl6)) | 0;
mid = (mid + Math.imul(al8, bh6)) | 0;
mid = (mid + Math.imul(ah8, bl6)) | 0;
hi = (hi + Math.imul(ah8, bh6)) | 0;
lo = (lo + Math.imul(al7, bl7)) | 0;
mid = (mid + Math.imul(al7, bh7)) | 0;
mid = (mid + Math.imul(ah7, bl7)) | 0;
hi = (hi + Math.imul(ah7, bh7)) | 0;
lo = (lo + Math.imul(al6, bl8)) | 0;
mid = (mid + Math.imul(al6, bh8)) | 0;
mid = (mid + Math.imul(ah6, bl8)) | 0;
hi = (hi + Math.imul(ah6, bh8)) | 0;
lo = (lo + Math.imul(al5, bl9)) | 0;
mid = (mid + Math.imul(al5, bh9)) | 0;
mid = (mid + Math.imul(ah5, bl9)) | 0;
hi = (hi + Math.imul(ah5, bh9)) | 0;
var w14 = (((c + lo) | 0) + ((mid & 0x1fff) << 13)) | 0;
c = (((hi + (mid >>> 13)) | 0) + (w14 >>> 26)) | 0;
w14 &= 0x3ffffff;
/* k = 15 */
lo = Math.imul(al9, bl6);
mid = Math.imul(al9, bh6);
mid = (mid + Math.imul(ah9, bl6)) | 0;
hi = Math.imul(ah9, bh6);
lo = (lo + Math.imul(al8, bl7)) | 0;
mid = (mid + Math.imul(al8, bh7)) | 0;
mid = (mid + Math.imul(ah8, bl7)) | 0;
hi = (hi + Math.imul(ah8, bh7)) | 0;
lo = (lo + Math.imul(al7, bl8)) | 0;
mid = (mid + Math.imul(al7, bh8)) | 0;
mid = (mid + Math.imul(ah7, bl8)) | 0;
hi = (hi + Math.imul(ah7, bh8)) | 0;
lo = (lo + Math.imul(al6, bl9)) | 0;
mid = (mid + Math.imul(al6, bh9)) | 0;
mid = (mid + Math.imul(ah6, bl9)) | 0;
hi = (hi + Math.imul(ah6, bh9)) | 0;
var w15 = (((c + lo) | 0) + ((mid & 0x1fff) << 13)) | 0;
c = (((hi + (mid >>> 13)) | 0) + (w15 >>> 26)) | 0;
w15 &= 0x3ffffff;
/* k = 16 */
lo = Math.imul(al9, bl7);
mid = Math.imul(al9, bh7);
mid = (mid + Math.imul(ah9, bl7)) | 0;
hi = Math.imul(ah9, bh7);
lo = (lo + Math.imul(al8, bl8)) | 0;
mid = (mid + Math.imul(al8, bh8)) | 0;
mid = (mid + Math.imul(ah8, bl8)) | 0;
hi = (hi + Math.imul(ah8, bh8)) | 0;
lo = (lo + Math.imul(al7, bl9)) | 0;
mid = (mid + Math.imul(al7, bh9)) | 0;
mid = (mid + Math.imul(ah7, bl9)) | 0;
hi = (hi + Math.imul(ah7, bh9)) | 0;
var w16 = (((c + lo) | 0) + ((mid & 0x1fff) << 13)) | 0;
c = (((hi + (mid >>> 13)) | 0) + (w16 >>> 26)) | 0;
w16 &= 0x3ffffff;
/* k = 17 */
lo = Math.imul(al9, bl8);
mid = Math.imul(al9, bh8);
mid = (mid + Math.imul(ah9, bl8)) | 0;
hi = Math.imul(ah9, bh8);
lo = (lo + Math.imul(al8, bl9)) | 0;
mid = (mid + Math.imul(al8, bh9)) | 0;
mid = (mid + Math.imul(ah8, bl9)) | 0;
hi = (hi + Math.imul(ah8, bh9)) | 0;
var w17 = (((c + lo) | 0) + ((mid & 0x1fff) << 13)) | 0;
c = (((hi + (mid >>> 13)) | 0) + (w17 >>> 26)) | 0;
w17 &= 0x3ffffff;
/* k = 18 */
lo = Math.imul(al9, bl9);
mid = Math.imul(al9, bh9);
mid = (mid + Math.imul(ah9, bl9)) | 0;
hi = Math.imul(ah9, bh9);
var w18 = (((c + lo) | 0) + ((mid & 0x1fff) << 13)) | 0;
c = (((hi + (mid >>> 13)) | 0) + (w18 >>> 26)) | 0;
w18 &= 0x3ffffff;
o[0] = w0;
o[1] = w1;
o[2] = w2;
o[3] = w3;
o[4] = w4;
o[5] = w5;
o[6] = w6;
o[7] = w7;
o[8] = w8;
o[9] = w9;
o[10] = w10;
o[11] = w11;
o[12] = w12;
o[13] = w13;
o[14] = w14;
o[15] = w15;
o[16] = w16;
o[17] = w17;
o[18] = w18;
if (c !== 0) {
o[19] = c;
out.length++;
}
return out;
};
// Polyfill comb
if (!Math.imul) {
comb10MulTo = smallMulTo;
}
function bigMulTo (self, num, out) {
out.negative = num.negative ^ self.negative;
out.length = self.length + num.length;
var carry = 0;
var hncarry = 0;
for (var k = 0; k < out.length - 1; k++) {
// Sum all words with the same `i + j = k` and accumulate `ncarry`,
// note that ncarry could be >= 0x3ffffff
var ncarry = hncarry;
hncarry = 0;
var rword = carry & 0x3ffffff;
var maxJ = Math.min(k, num.length - 1);
for (var j = Math.max(0, k - self.length + 1); j <= maxJ; j++) {
var i = k - j;
var a = self.words[i] | 0;
var b = num.words[j] | 0;
var r = a * b;
var lo = r & 0x3ffffff;
ncarry = (ncarry + ((r / 0x4000000) | 0)) | 0;
lo = (lo + rword) | 0;
rword = lo & 0x3ffffff;
ncarry = (ncarry + (lo >>> 26)) | 0;
hncarry += ncarry >>> 26;
ncarry &= 0x3ffffff;
}
out.words[k] = rword;
carry = ncarry;
ncarry = hncarry;
}
if (carry !== 0) {
out.words[k] = carry;
} else {
out.length--;
}
return out.strip();
}
function jumboMulTo (self, num, out) {
var fftm = new FFTM();
return fftm.mulp(self, num, out);
}
BN.prototype.mulTo = function mulTo (num, out) {
var res;
var len = this.length + num.length;
if (this.length === 10 && num.length === 10) {
res = comb10MulTo(this, num, out);
} else if (len < 63) {
res = smallMulTo(this, num, out);
} else if (len < 1024) {
res = bigMulTo(this, num, out);
} else {
res = jumboMulTo(this, num, out);
}
return res;
};
// Cooley-Tukey algorithm for FFT
// slightly revisited to rely on looping instead of recursion
function FFTM (x, y) {
this.x = x;
this.y = y;
}
FFTM.prototype.makeRBT = function makeRBT (N) {
var t = new Array(N);
var l = BN.prototype._countBits(N) - 1;
for (var i = 0; i < N; i++) {
t[i] = this.revBin(i, l, N);
}
return t;
};
// Returns binary-reversed representation of `x`
FFTM.prototype.revBin = function revBin (x, l, N) {
if (x === 0 || x === N - 1) return x;
var rb = 0;
for (var i = 0; i < l; i++) {
rb |= (x & 1) << (l - i - 1);
x >>= 1;
}
return rb;
};
// Performs "tweedling" phase, therefore 'emulating'
// behaviour of the recursive algorithm
FFTM.prototype.permute = function permute (rbt, rws, iws, rtws, itws, N) {
for (var i = 0; i < N; i++) {
rtws[i] = rws[rbt[i]];
itws[i] = iws[rbt[i]];
}
};
FFTM.prototype.transform = function transform (rws, iws, rtws, itws, N, rbt) {
this.permute(rbt, rws, iws, rtws, itws, N);
for (var s = 1; s < N; s <<= 1) {
var l = s << 1;
var rtwdf = Math.cos(2 * Math.PI / l);
var itwdf = Math.sin(2 * Math.PI / l);
for (var p = 0; p < N; p += l) {
var rtwdf_ = rtwdf;
var itwdf_ = itwdf;
for (var j = 0; j < s; j++) {
var re = rtws[p + j];
var ie = itws[p + j];
var ro = rtws[p + j + s];
var io = itws[p + j + s];
var rx = rtwdf_ * ro - itwdf_ * io;
io = rtwdf_ * io + itwdf_ * ro;
ro = rx;
rtws[p + j] = re + ro;
itws[p + j] = ie + io;
rtws[p + j + s] = re - ro;
itws[p + j + s] = ie - io;
/* jshint maxdepth : false */
if (j !== l) {
rx = rtwdf * rtwdf_ - itwdf * itwdf_;
itwdf_ = rtwdf * itwdf_ + itwdf * rtwdf_;
rtwdf_ = rx;
}
}
}
}
};
FFTM.prototype.guessLen13b = function guessLen13b (n, m) {
var N = Math.max(m, n) | 1;
var odd = N & 1;
var i = 0;
for (N = N / 2 | 0; N; N = N >>> 1) {
i++;
}
return 1 << i + 1 + odd;
};
FFTM.prototype.conjugate = function conjugate (rws, iws, N) {
if (N <= 1) return;
for (var i = 0; i < N / 2; i++) {
var t = rws[i];
rws[i] = rws[N - i - 1];
rws[N - i - 1] = t;
t = iws[i];
iws[i] = -iws[N - i - 1];
iws[N - i - 1] = -t;
}
};
FFTM.prototype.normalize13b = function normalize13b (ws, N) {
var carry = 0;
for (var i = 0; i < N / 2; i++) {
var w = Math.round(ws[2 * i + 1] / N) * 0x2000 +
Math.round(ws[2 * i] / N) +
carry;
ws[i] = w & 0x3ffffff;
if (w < 0x4000000) {
carry = 0;
} else {
carry = w / 0x4000000 | 0;
}
}
return ws;
};
FFTM.prototype.convert13b = function convert13b (ws, len, rws, N) {
var carry = 0;
for (var i = 0; i < len; i++) {
carry = carry + (ws[i] | 0);
rws[2 * i] = carry & 0x1fff; carry = carry >>> 13;
rws[2 * i + 1] = carry & 0x1fff; carry = carry >>> 13;
}
// Pad with zeroes
for (i = 2 * len; i < N; ++i) {
rws[i] = 0;
}
assert(carry === 0);
assert((carry & ~0x1fff) === 0);
};
FFTM.prototype.stub = function stub (N) {
var ph = new Array(N);
for (var i = 0; i < N; i++) {
ph[i] = 0;
}
return ph;
};
FFTM.prototype.mulp = function mulp (x, y, out) {
var N = 2 * this.guessLen13b(x.length, y.length);
var rbt = this.makeRBT(N);
var _ = this.stub(N);
var rws = new Array(N);
var rwst = new Array(N);
var iwst = new Array(N);
var nrws = new Array(N);
var nrwst = new Array(N);
var niwst = new Array(N);
var rmws = out.words;
rmws.length = N;
this.convert13b(x.words, x.length, rws, N);
this.convert13b(y.words, y.length, nrws, N);
this.transform(rws, _, rwst, iwst, N, rbt);
this.transform(nrws, _, nrwst, niwst, N, rbt);
for (var i = 0; i < N; i++) {
var rx = rwst[i] * nrwst[i] - iwst[i] * niwst[i];
iwst[i] = rwst[i] * niwst[i] + iwst[i] * nrwst[i];
rwst[i] = rx;
}
this.conjugate(rwst, iwst, N);
this.transform(rwst, iwst, rmws, _, N, rbt);
this.conjugate(rmws, _, N);
this.normalize13b(rmws, N);
out.negative = x.negative ^ y.negative;
out.length = x.length + y.length;
return out.strip();
};
// Multiply `this` by `num`
BN.prototype.mul = function mul (num) {
var out = new BN(null);
out.words = new Array(this.length + num.length);
return this.mulTo(num, out);
};
// Multiply employing FFT
BN.prototype.mulf = function mulf (num) {
var out = new BN(null);
out.words = new Array(this.length + num.length);
return jumboMulTo(this, num, out);
};
// In-place Multiplication
BN.prototype.imul = function imul (num) {
return this.clone().mulTo(num, this);
};
BN.prototype.imuln = function imuln (num) {
assert(typeof num === 'number');
assert(num < 0x4000000);
// Carry
var carry = 0;
for (var i = 0; i < this.length; i++) {
var w = (this.words[i] | 0) * num;
var lo = (w & 0x3ffffff) + (carry & 0x3ffffff);
carry >>= 26;
carry += (w / 0x4000000) | 0;
// NOTE: lo is 27bit maximum
carry += lo >>> 26;
this.words[i] = lo & 0x3ffffff;
}
if (carry !== 0) {
this.words[i] = carry;
this.length++;
}
return this;
};
BN.prototype.muln = function muln (num) {
return this.clone().imuln(num);
};
// `this` * `this`
BN.prototype.sqr = function sqr () {
return this.mul(this);
};
// `this` * `this` in-place
BN.prototype.isqr = function isqr () {
return this.imul(this.clone());
};
// Math.pow(`this`, `num`)
BN.prototype.pow = function pow (num) {
var w = toBitArray(num);
if (w.length === 0) return new BN(1);
// Skip leading zeroes
var res = this;
for (var i = 0; i < w.length; i++, res = res.sqr()) {
if (w[i] !== 0) break;
}
if (++i < w.length) {
for (var q = res.sqr(); i < w.length; i++, q = q.sqr()) {
if (w[i] === 0) continue;
res = res.mul(q);
}
}
return res;
};
// Shift-left in-place
BN.prototype.iushln = function iushln (bits) {
assert(typeof bits === 'number' && bits >= 0);
var r = bits % 26;
var s = (bits - r) / 26;
var carryMask = (0x3ffffff >>> (26 - r)) << (26 - r);
var i;
if (r !== 0) {
var carry = 0;
for (i = 0; i < this.length; i++) {
var newCarry = this.words[i] & carryMask;
var c = ((this.words[i] | 0) - newCarry) << r;
this.words[i] = c | carry;
carry = newCarry >>> (26 - r);
}
if (carry) {
this.words[i] = carry;
this.length++;
}
}
if (s !== 0) {
for (i = this.length - 1; i >= 0; i--) {
this.words[i + s] = this.words[i];
}
for (i = 0; i < s; i++) {
this.words[i] = 0;
}
this.length += s;
}
return this.strip();
};
BN.prototype.ishln = function ishln (bits) {
// TODO(indutny): implement me
assert(this.negative === 0);
return this.iushln(bits);
};
// Shift-right in-place
// NOTE: `hint` is a lowest bit before trailing zeroes
// NOTE: if `extended` is present - it will be filled with destroyed bits
BN.prototype.iushrn = function iushrn (bits, hint, extended) {
assert(typeof bits === 'number' && bits >= 0);
var h;
if (hint) {
h = (hint - (hint % 26)) / 26;
} else {
h = 0;
}
var r = bits % 26;
var s = Math.min((bits - r) / 26, this.length);
var mask = 0x3ffffff ^ ((0x3ffffff >>> r) << r);
var maskedWords = extended;
h -= s;
h = Math.max(0, h);
// Extended mode, copy masked part
if (maskedWords) {
for (var i = 0; i < s; i++) {
maskedWords.words[i] = this.words[i];
}
maskedWords.length = s;
}
if (s === 0) {
// No-op, we should not move anything at all
} else if (this.length > s) {
this.length -= s;
for (i = 0; i < this.length; i++) {
this.words[i] = this.words[i + s];
}
} else {
this.words[0] = 0;
this.length = 1;
}
var carry = 0;
for (i = this.length - 1; i >= 0 && (carry !== 0 || i >= h); i--) {
var word = this.words[i] | 0;
this.words[i] = (carry << (26 - r)) | (word >>> r);
carry = word & mask;
}
// Push carried bits as a mask
if (maskedWords && carry !== 0) {
maskedWords.words[maskedWords.length++] = carry;
}
if (this.length === 0) {
this.words[0] = 0;
this.length = 1;
}
return this.strip();
};
BN.prototype.ishrn = function ishrn (bits, hint, extended) {
// TODO(indutny): implement me
assert(this.negative === 0);
return this.iushrn(bits, hint, extended);
};
// Shift-left
BN.prototype.shln = function shln (bits) {
return this.clone().ishln(bits);
};
BN.prototype.ushln = function ushln (bits) {
return this.clone().iushln(bits);
};
// Shift-right
BN.prototype.shrn = function shrn (bits) {
return this.clone().ishrn(bits);
};
BN.prototype.ushrn = function ushrn (bits) {
return this.clone().iushrn(bits);
};
// Test if n bit is set
BN.prototype.testn = function testn (bit) {
assert(typeof bit === 'number' && bit >= 0);
var r = bit % 26;
var s = (bit - r) / 26;
var q = 1 << r;
// Fast case: bit is much higher than all existing words
if (this.length <= s) return false;
// Check bit and return
var w = this.words[s];
return !!(w & q);
};
// Return only lowers bits of number (in-place)
BN.prototype.imaskn = function imaskn (bits) {
assert(typeof bits === 'number' && bits >= 0);
var r = bits % 26;
var s = (bits - r) / 26;
assert(this.negative === 0, 'imaskn works only with positive numbers');
if (this.length <= s) {
return this;
}
if (r !== 0) {
s++;
}
this.length = Math.min(s, this.length);
if (r !== 0) {
var mask = 0x3ffffff ^ ((0x3ffffff >>> r) << r);
this.words[this.length - 1] &= mask;
}
return this.strip();
};
// Return only lowers bits of number
BN.prototype.maskn = function maskn (bits) {
return this.clone().imaskn(bits);
};
// Add plain number `num` to `this`
BN.prototype.iaddn = function iaddn (num) {
assert(typeof num === 'number');
assert(num < 0x4000000);
if (num < 0) return this.isubn(-num);
// Possible sign change
if (this.negative !== 0) {
if (this.length === 1 && (this.words[0] | 0) < num) {
this.words[0] = num - (this.words[0] | 0);
this.negative = 0;
return this;
}
this.negative = 0;
this.isubn(num);
this.negative = 1;
return this;
}
// Add without checks
return this._iaddn(num);
};
BN.prototype._iaddn = function _iaddn (num) {
this.words[0] += num;
// Carry
for (var i = 0; i < this.length && this.words[i] >= 0x4000000; i++) {
this.words[i] -= 0x4000000;
if (i === this.length - 1) {
this.words[i + 1] = 1;
} else {
this.words[i + 1]++;
}
}
this.length = Math.max(this.length, i + 1);
return this;
};
// Subtract plain number `num` from `this`
BN.prototype.isubn = function isubn (num) {
assert(typeof num === 'number');
assert(num < 0x4000000);
if (num < 0) return this.iaddn(-num);
if (this.negative !== 0) {
this.negative = 0;
this.iaddn(num);
this.negative = 1;
return this;
}
this.words[0] -= num;
if (this.length === 1 && this.words[0] < 0) {
this.words[0] = -this.words[0];
this.negative = 1;
} else {
// Carry
for (var i = 0; i < this.length && this.words[i] < 0; i++) {
this.words[i] += 0x4000000;
this.words[i + 1] -= 1;
}
}
return this.strip();
};
BN.prototype.addn = function addn (num) {
return this.clone().iaddn(num);
};
BN.prototype.subn = function subn (num) {
return this.clone().isubn(num);
};
BN.prototype.iabs = function iabs () {
this.negative = 0;
return this;
};
BN.prototype.abs = function abs () {
return this.clone().iabs();
};
BN.prototype._ishlnsubmul = function _ishlnsubmul (num, mul, shift) {
var len = num.length + shift;
var i;
this._expand(len);
var w;
var carry = 0;
for (i = 0; i < num.length; i++) {
w = (this.words[i + shift] | 0) + carry;
var right = (num.words[i] | 0) * mul;
w -= right & 0x3ffffff;
carry = (w >> 26) - ((right / 0x4000000) | 0);
this.words[i + shift] = w & 0x3ffffff;
}
for (; i < this.length - shift; i++) {
w = (this.words[i + shift] | 0) + carry;
carry = w >> 26;
this.words[i + shift] = w & 0x3ffffff;
}
if (carry === 0) return this.strip();
// Subtraction overflow
assert(carry === -1);
carry = 0;
for (i = 0; i < this.length; i++) {
w = -(this.words[i] | 0) + carry;
carry = w >> 26;
this.words[i] = w & 0x3ffffff;
}
this.negative = 1;
return this.strip();
};
BN.prototype._wordDiv = function _wordDiv (num, mode) {
var shift = this.length - num.length;
var a = this.clone();
var b = num;
// Normalize
var bhi = b.words[b.length - 1] | 0;
var bhiBits = this._countBits(bhi);
shift = 26 - bhiBits;
if (shift !== 0) {
b = b.ushln(shift);
a.iushln(shift);
bhi = b.words[b.length - 1] | 0;
}
// Initialize quotient
var m = a.length - b.length;
var q;
if (mode !== 'mod') {
q = new BN(null);
q.length = m + 1;
q.words = new Array(q.length);
for (var i = 0; i < q.length; i++) {
q.words[i] = 0;
}
}
var diff = a.clone()._ishlnsubmul(b, 1, m);
if (diff.negative === 0) {
a = diff;
if (q) {
q.words[m] = 1;
}
}
for (var j = m - 1; j >= 0; j--) {
var qj = (a.words[b.length + j] | 0) * 0x4000000 +
(a.words[b.length + j - 1] | 0);
// NOTE: (qj / bhi) is (0x3ffffff * 0x4000000 + 0x3ffffff) / 0x2000000 max
// (0x7ffffff)
qj = Math.min((qj / bhi) | 0, 0x3ffffff);
a._ishlnsubmul(b, qj, j);
while (a.negative !== 0) {
qj--;
a.negative = 0;
a._ishlnsubmul(b, 1, j);
if (!a.isZero()) {
a.negative ^= 1;
}
}
if (q) {
q.words[j] = qj;
}
}
if (q) {
q.strip();
}
a.strip();
// Denormalize
if (mode !== 'div' && shift !== 0) {
a.iushrn(shift);
}
return {
div: q || null,
mod: a
};
};
// NOTE: 1) `mode` can be set to `mod` to request mod only,
// to `div` to request div only, or be absent to
// request both div & mod
// 2) `positive` is true if unsigned mod is requested
BN.prototype.divmod = function divmod (num, mode, positive) {
assert(!num.isZero());
if (this.isZero()) {
return {
div: new BN(0),
mod: new BN(0)
};
}
var div, mod, res;
if (this.negative !== 0 && num.negative === 0) {
res = this.neg().divmod(num, mode);
if (mode !== 'mod') {
div = res.div.neg();
}
if (mode !== 'div') {
mod = res.mod.neg();
if (positive && mod.negative !== 0) {
mod.iadd(num);
}
}
return {
div: div,
mod: mod
};
}
if (this.negative === 0 && num.negative !== 0) {
res = this.divmod(num.neg(), mode);
if (mode !== 'mod') {
div = res.div.neg();
}
return {
div: div,
mod: res.mod
};
}
if ((this.negative & num.negative) !== 0) {
res = this.neg().divmod(num.neg(), mode);
if (mode !== 'div') {
mod = res.mod.neg();
if (positive && mod.negative !== 0) {
mod.isub(num);
}
}
return {
div: res.div,
mod: mod
};
}
// Both numbers are positive at this point
// Strip both numbers to approximate shift value
if (num.length > this.length || this.cmp(num) < 0) {
return {
div: new BN(0),
mod: this
};
}
// Very short reduction
if (num.length === 1) {
if (mode === 'div') {
return {
div: this.divn(num.words[0]),
mod: null
};
}
if (mode === 'mod') {
return {
div: null,
mod: new BN(this.modn(num.words[0]))
};
}
return {
div: this.divn(num.words[0]),
mod: new BN(this.modn(num.words[0]))
};
}
return this._wordDiv(num, mode);
};
// Find `this` / `num`
BN.prototype.div = function div (num) {
return this.divmod(num, 'div', false).div;
};
// Find `this` % `num`
BN.prototype.mod = function mod (num) {
return this.divmod(num, 'mod', false).mod;
};
BN.prototype.umod = function umod (num) {
return this.divmod(num, 'mod', true).mod;
};
// Find Round(`this` / `num`)
BN.prototype.divRound = function divRound (num) {
var dm = this.divmod(num);
// Fast case - exact division
if (dm.mod.isZero()) return dm.div;
var mod = dm.div.negative !== 0 ? dm.mod.isub(num) : dm.mod;
var half = num.ushrn(1);
var r2 = num.andln(1);
var cmp = mod.cmp(half);
// Round down
if (cmp < 0 || r2 === 1 && cmp === 0) return dm.div;
// Round up
return dm.div.negative !== 0 ? dm.div.isubn(1) : dm.div.iaddn(1);
};
BN.prototype.modn = function modn (num) {
assert(num <= 0x3ffffff);
var p = (1 << 26) % num;
var acc = 0;
for (var i = this.length - 1; i >= 0; i--) {
acc = (p * acc + (this.words[i] | 0)) % num;
}
return acc;
};
// In-place division by number
BN.prototype.idivn = function idivn (num) {
assert(num <= 0x3ffffff);
var carry = 0;
for (var i = this.length - 1; i >= 0; i--) {
var w = (this.words[i] | 0) + carry * 0x4000000;
this.words[i] = (w / num) | 0;
carry = w % num;
}
return this.strip();
};
BN.prototype.divn = function divn (num) {
return this.clone().idivn(num);
};
BN.prototype.egcd = function egcd (p) {
assert(p.negative === 0);
assert(!p.isZero());
var x = this;
var y = p.clone();
if (x.negative !== 0) {
x = x.umod(p);
} else {
x = x.clone();
}
// A * x + B * y = x
var A = new BN(1);
var B = new BN(0);
// C * x + D * y = y
var C = new BN(0);
var D = new BN(1);
var g = 0;
while (x.isEven() && y.isEven()) {
x.iushrn(1);
y.iushrn(1);
++g;
}
var yp = y.clone();
var xp = x.clone();
while (!x.isZero()) {
for (var i = 0, im = 1; (x.words[0] & im) === 0 && i < 26; ++i, im <<= 1);
if (i > 0) {
x.iushrn(i);
while (i-- > 0) {
if (A.isOdd() || B.isOdd()) {
A.iadd(yp);
B.isub(xp);
}
A.iushrn(1);
B.iushrn(1);
}
}
for (var j = 0, jm = 1; (y.words[0] & jm) === 0 && j < 26; ++j, jm <<= 1);
if (j > 0) {
y.iushrn(j);
while (j-- > 0) {
if (C.isOdd() || D.isOdd()) {
C.iadd(yp);
D.isub(xp);
}
C.iushrn(1);
D.iushrn(1);
}
}
if (x.cmp(y) >= 0) {
x.isub(y);
A.isub(C);
B.isub(D);
} else {
y.isub(x);
C.isub(A);
D.isub(B);
}
}
return {
a: C,
b: D,
gcd: y.iushln(g)
};
};
// This is reduced incarnation of the binary EEA
// above, designated to invert members of the
// _prime_ fields F(p) at a maximal speed
BN.prototype._invmp = function _invmp (p) {
assert(p.negative === 0);
assert(!p.isZero());
var a = this;
var b = p.clone();
if (a.negative !== 0) {
a = a.umod(p);
} else {
a = a.clone();
}
var x1 = new BN(1);
var x2 = new BN(0);
var delta = b.clone();
while (a.cmpn(1) > 0 && b.cmpn(1) > 0) {
for (var i = 0, im = 1; (a.words[0] & im) === 0 && i < 26; ++i, im <<= 1);
if (i > 0) {
a.iushrn(i);
while (i-- > 0) {
if (x1.isOdd()) {
x1.iadd(delta);
}
x1.iushrn(1);
}
}
for (var j = 0, jm = 1; (b.words[0] & jm) === 0 && j < 26; ++j, jm <<= 1);
if (j > 0) {
b.iushrn(j);
while (j-- > 0) {
if (x2.isOdd()) {
x2.iadd(delta);
}
x2.iushrn(1);
}
}
if (a.cmp(b) >= 0) {
a.isub(b);
x1.isub(x2);
} else {
b.isub(a);
x2.isub(x1);
}
}
var res;
if (a.cmpn(1) === 0) {
res = x1;
} else {
res = x2;
}
if (res.cmpn(0) < 0) {
res.iadd(p);
}
return res;
};
BN.prototype.gcd = function gcd (num) {
if (this.isZero()) return num.abs();
if (num.isZero()) return this.abs();
var a = this.clone();
var b = num.clone();
a.negative = 0;
b.negative = 0;
// Remove common factor of two
for (var shift = 0; a.isEven() && b.isEven(); shift++) {
a.iushrn(1);
b.iushrn(1);
}
do {
while (a.isEven()) {
a.iushrn(1);
}
while (b.isEven()) {
b.iushrn(1);
}
var r = a.cmp(b);
if (r < 0) {
// Swap `a` and `b` to make `a` always bigger than `b`
var t = a;
a = b;
b = t;
} else if (r === 0 || b.cmpn(1) === 0) {
break;
}
a.isub(b);
} while (true);
return b.iushln(shift);
};
// Invert number in the field F(num)
BN.prototype.invm = function invm (num) {
return this.egcd(num).a.umod(num);
};
BN.prototype.isEven = function isEven () {
return (this.words[0] & 1) === 0;
};
BN.prototype.isOdd = function isOdd () {
return (this.words[0] & 1) === 1;
};
// And first word and num
BN.prototype.andln = function andln (num) {
return this.words[0] & num;
};
// Increment at the bit position in-line
BN.prototype.bincn = function bincn (bit) {
assert(typeof bit === 'number');
var r = bit % 26;
var s = (bit - r) / 26;
var q = 1 << r;
// Fast case: bit is much higher than all existing words
if (this.length <= s) {
this._expand(s + 1);
this.words[s] |= q;
return this;
}
// Add bit and propagate, if needed
var carry = q;
for (var i = s; carry !== 0 && i < this.length; i++) {
var w = this.words[i] | 0;
w += carry;
carry = w >>> 26;
w &= 0x3ffffff;
this.words[i] = w;
}
if (carry !== 0) {
this.words[i] = carry;
this.length++;
}
return this;
};
BN.prototype.isZero = function isZero () {
return this.length === 1 && this.words[0] === 0;
};
BN.prototype.cmpn = function cmpn (num) {
var negative = num < 0;
if (this.negative !== 0 && !negative) return -1;
if (this.negative === 0 && negative) return 1;
this.strip();
var res;
if (this.length > 1) {
res = 1;
} else {
if (negative) {
num = -num;
}
assert(num <= 0x3ffffff, 'Number is too big');
var w = this.words[0] | 0;
res = w === num ? 0 : w < num ? -1 : 1;
}
if (this.negative !== 0) return -res | 0;
return res;
};
// Compare two numbers and return:
// 1 - if `this` > `num`
// 0 - if `this` == `num`
// -1 - if `this` < `num`
BN.prototype.cmp = function cmp (num) {
if (this.negative !== 0 && num.negative === 0) return -1;
if (this.negative === 0 && num.negative !== 0) return 1;
var res = this.ucmp(num);
if (this.negative !== 0) return -res | 0;
return res;
};
// Unsigned comparison
BN.prototype.ucmp = function ucmp (num) {
// At this point both numbers have the same sign
if (this.length > num.length) return 1;
if (this.length < num.length) return -1;
var res = 0;
for (var i = this.length - 1; i >= 0; i--) {
var a = this.words[i] | 0;
var b = num.words[i] | 0;
if (a === b) continue;
if (a < b) {
res = -1;
} else if (a > b) {
res = 1;
}
break;
}
return res;
};
BN.prototype.gtn = function gtn (num) {
return this.cmpn(num) === 1;
};
BN.prototype.gt = function gt (num) {
return this.cmp(num) === 1;
};
BN.prototype.gten = function gten (num) {
return this.cmpn(num) >= 0;
};
BN.prototype.gte = function gte (num) {
return this.cmp(num) >= 0;
};
BN.prototype.ltn = function ltn (num) {
return this.cmpn(num) === -1;
};
BN.prototype.lt = function lt (num) {
return this.cmp(num) === -1;
};
BN.prototype.lten = function lten (num) {
return this.cmpn(num) <= 0;
};
BN.prototype.lte = function lte (num) {
return this.cmp(num) <= 0;
};
BN.prototype.eqn = function eqn (num) {
return this.cmpn(num) === 0;
};
BN.prototype.eq = function eq (num) {
return this.cmp(num) === 0;
};
//
// A reduce context, could be using montgomery or something better, depending
// on the `m` itself.
//
BN.red = function red (num) {
return new Red(num);
};
BN.prototype.toRed = function toRed (ctx) {
assert(!this.red, 'Already a number in reduction context');
assert(this.negative === 0, 'red works only with positives');
return ctx.convertTo(this)._forceRed(ctx);
};
BN.prototype.fromRed = function fromRed () {
assert(this.red, 'fromRed works only with numbers in reduction context');
return this.red.convertFrom(this);
};
BN.prototype._forceRed = function _forceRed (ctx) {
this.red = ctx;
return this;
};
BN.prototype.forceRed = function forceRed (ctx) {
assert(!this.red, 'Already a number in reduction context');
return this._forceRed(ctx);
};
BN.prototype.redAdd = function redAdd (num) {
assert(this.red, 'redAdd works only with red numbers');
return this.red.add(this, num);
};
BN.prototype.redIAdd = function redIAdd (num) {
assert(this.red, 'redIAdd works only with red numbers');
return this.red.iadd(this, num);
};
BN.prototype.redSub = function redSub (num) {
assert(this.red, 'redSub works only with red numbers');
return this.red.sub(this, num);
};
BN.prototype.redISub = function redISub (num) {
assert(this.red, 'redISub works only with red numbers');
return this.red.isub(this, num);
};
BN.prototype.redShl = function redShl (num) {
assert(this.red, 'redShl works only with red numbers');
return this.red.shl(this, num);
};
BN.prototype.redMul = function redMul (num) {
assert(this.red, 'redMul works only with red numbers');
this.red._verify2(this, num);
return this.red.mul(this, num);
};
BN.prototype.redIMul = function redIMul (num) {
assert(this.red, 'redMul works only with red numbers');
this.red._verify2(this, num);
return this.red.imul(this, num);
};
BN.prototype.redSqr = function redSqr () {
assert(this.red, 'redSqr works only with red numbers');
this.red._verify1(this);
return this.red.sqr(this);
};
BN.prototype.redISqr = function redISqr () {
assert(this.red, 'redISqr works only with red numbers');
this.red._verify1(this);
return this.red.isqr(this);
};
// Square root over p
BN.prototype.redSqrt = function redSqrt () {
assert(this.red, 'redSqrt works only with red numbers');
this.red._verify1(this);
return this.red.sqrt(this);
};
BN.prototype.redInvm = function redInvm () {
assert(this.red, 'redInvm works only with red numbers');
this.red._verify1(this);
return this.red.invm(this);
};
// Return negative clone of `this` % `red modulo`
BN.prototype.redNeg = function redNeg () {
assert(this.red, 'redNeg works only with red numbers');
this.red._verify1(this);
return this.red.neg(this);
};
BN.prototype.redPow = function redPow (num) {
assert(this.red && !num.red, 'redPow(normalNum)');
this.red._verify1(this);
return this.red.pow(this, num);
};
// Prime numbers with efficient reduction
var primes = {
k256: null,
p224: null,
p192: null,
p25519: null
};
// Pseudo-Mersenne prime
function MPrime (name, p) {
// P = 2 ^ N - K
this.name = name;
this.p = new BN(p, 16);
this.n = this.p.bitLength();
this.k = new BN(1).iushln(this.n).isub(this.p);
this.tmp = this._tmp();
}
MPrime.prototype._tmp = function _tmp () {
var tmp = new BN(null);
tmp.words = new Array(Math.ceil(this.n / 13));
return tmp;
};
MPrime.prototype.ireduce = function ireduce (num) {
// Assumes that `num` is less than `P^2`
// num = HI * (2 ^ N - K) + HI * K + LO = HI * K + LO (mod P)
var r = num;
var rlen;
do {
this.split(r, this.tmp);
r = this.imulK(r);
r = r.iadd(this.tmp);
rlen = r.bitLength();
} while (rlen > this.n);
var cmp = rlen < this.n ? -1 : r.ucmp(this.p);
if (cmp === 0) {
r.words[0] = 0;
r.length = 1;
} else if (cmp > 0) {
r.isub(this.p);
} else {
if (r.strip !== undefined) {
// r is BN v4 instance
r.strip();
} else {
// r is BN v5 instance
r._strip();
}
}
return r;
};
MPrime.prototype.split = function split (input, out) {
input.iushrn(this.n, 0, out);
};
MPrime.prototype.imulK = function imulK (num) {
return num.imul(this.k);
};
function K256 () {
MPrime.call(
this,
'k256',
'ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff fffffffe fffffc2f');
}
inherits(K256, MPrime);
K256.prototype.split = function split (input, output) {
// 256 = 9 * 26 + 22
var mask = 0x3fffff;
var outLen = Math.min(input.length, 9);
for (var i = 0; i < outLen; i++) {
output.words[i] = input.words[i];
}
output.length = outLen;
if (input.length <= 9) {
input.words[0] = 0;
input.length = 1;
return;
}
// Shift by 9 limbs
var prev = input.words[9];
output.words[output.length++] = prev & mask;
for (i = 10; i < input.length; i++) {
var next = input.words[i] | 0;
input.words[i - 10] = ((next & mask) << 4) | (prev >>> 22);
prev = next;
}
prev >>>= 22;
input.words[i - 10] = prev;
if (prev === 0 && input.length > 10) {
input.length -= 10;
} else {
input.length -= 9;
}
};
K256.prototype.imulK = function imulK (num) {
// K = 0x1000003d1 = [ 0x40, 0x3d1 ]
num.words[num.length] = 0;
num.words[num.length + 1] = 0;
num.length += 2;
// bounded at: 0x40 * 0x3ffffff + 0x3d0 = 0x100000390
var lo = 0;
for (var i = 0; i < num.length; i++) {
var w = num.words[i] | 0;
lo += w * 0x3d1;
num.words[i] = lo & 0x3ffffff;
lo = w * 0x40 + ((lo / 0x4000000) | 0);
}
// Fast length reduction
if (num.words[num.length - 1] === 0) {
num.length--;
if (num.words[num.length - 1] === 0) {
num.length--;
}
}
return num;
};
function P224 () {
MPrime.call(
this,
'p224',
'ffffffff ffffffff ffffffff ffffffff 00000000 00000000 00000001');
}
inherits(P224, MPrime);
function P192 () {
MPrime.call(
this,
'p192',
'ffffffff ffffffff ffffffff fffffffe ffffffff ffffffff');
}
inherits(P192, MPrime);
function P25519 () {
// 2 ^ 255 - 19
MPrime.call(
this,
'25519',
'7fffffffffffffff ffffffffffffffff ffffffffffffffff ffffffffffffffed');
}
inherits(P25519, MPrime);
P25519.prototype.imulK = function imulK (num) {
// K = 0x13
var carry = 0;
for (var i = 0; i < num.length; i++) {
var hi = (num.words[i] | 0) * 0x13 + carry;
var lo = hi & 0x3ffffff;
hi >>>= 26;
num.words[i] = lo;
carry = hi;
}
if (carry !== 0) {
num.words[num.length++] = carry;
}
return num;
};
// Exported mostly for testing purposes, use plain name instead
BN._prime = function prime (name) {
// Cached version of prime
if (primes[name]) return primes[name];
var prime;
if (name === 'k256') {
prime = new K256();
} else if (name === 'p224') {
prime = new P224();
} else if (name === 'p192') {
prime = new P192();
} else if (name === 'p25519') {
prime = new P25519();
} else {
throw new Error('Unknown prime ' + name);
}
primes[name] = prime;
return prime;
};
//
// Base reduction engine
//
function Red (m) {
if (typeof m === 'string') {
var prime = BN._prime(m);
this.m = prime.p;
this.prime = prime;
} else {
assert(m.gtn(1), 'modulus must be greater than 1');
this.m = m;
this.prime = null;
}
}
Red.prototype._verify1 = function _verify1 (a) {
assert(a.negative === 0, 'red works only with positives');
assert(a.red, 'red works only with red numbers');
};
Red.prototype._verify2 = function _verify2 (a, b) {
assert((a.negative | b.negative) === 0, 'red works only with positives');
assert(a.red && a.red === b.red,
'red works only with red numbers');
};
Red.prototype.imod = function imod (a) {
if (this.prime) return this.prime.ireduce(a)._forceRed(this);
return a.umod(this.m)._forceRed(this);
};
Red.prototype.neg = function neg (a) {
if (a.isZero()) {
return a.clone();
}
return this.m.sub(a)._forceRed(this);
};
Red.prototype.add = function add (a, b) {
this._verify2(a, b);
var res = a.add(b);
if (res.cmp(this.m) >= 0) {
res.isub(this.m);
}
return res._forceRed(this);
};
Red.prototype.iadd = function iadd (a, b) {
this._verify2(a, b);
var res = a.iadd(b);
if (res.cmp(this.m) >= 0) {
res.isub(this.m);
}
return res;
};
Red.prototype.sub = function sub (a, b) {
this._verify2(a, b);
var res = a.sub(b);
if (res.cmpn(0) < 0) {
res.iadd(this.m);
}
return res._forceRed(this);
};
Red.prototype.isub = function isub (a, b) {
this._verify2(a, b);
var res = a.isub(b);
if (res.cmpn(0) < 0) {
res.iadd(this.m);
}
return res;
};
Red.prototype.shl = function shl (a, num) {
this._verify1(a);
return this.imod(a.ushln(num));
};
Red.prototype.imul = function imul (a, b) {
this._verify2(a, b);
return this.imod(a.imul(b));
};
Red.prototype.mul = function mul (a, b) {
this._verify2(a, b);
return this.imod(a.mul(b));
};
Red.prototype.isqr = function isqr (a) {
return this.imul(a, a.clone());
};
Red.prototype.sqr = function sqr (a) {
return this.mul(a, a);
};
Red.prototype.sqrt = function sqrt (a) {
if (a.isZero()) return a.clone();
var mod3 = this.m.andln(3);
assert(mod3 % 2 === 1);
// Fast case
if (mod3 === 3) {
var pow = this.m.add(new BN(1)).iushrn(2);
return this.pow(a, pow);
}
// Tonelli-Shanks algorithm (Totally unoptimized and slow)
//
// Find Q and S, that Q * 2 ^ S = (P - 1)
var q = this.m.subn(1);
var s = 0;
while (!q.isZero() && q.andln(1) === 0) {
s++;
q.iushrn(1);
}
assert(!q.isZero());
var one = new BN(1).toRed(this);
var nOne = one.redNeg();
// Find quadratic non-residue
// NOTE: Max is such because of generalized Riemann hypothesis.
var lpow = this.m.subn(1).iushrn(1);
var z = this.m.bitLength();
z = new BN(2 * z * z).toRed(this);
while (this.pow(z, lpow).cmp(nOne) !== 0) {
z.redIAdd(nOne);
}
var c = this.pow(z, q);
var r = this.pow(a, q.addn(1).iushrn(1));
var t = this.pow(a, q);
var m = s;
while (t.cmp(one) !== 0) {
var tmp = t;
for (var i = 0; tmp.cmp(one) !== 0; i++) {
tmp = tmp.redSqr();
}
assert(i < m);
var b = this.pow(c, new BN(1).iushln(m - i - 1));
r = r.redMul(b);
c = b.redSqr();
t = t.redMul(c);
m = i;
}
return r;
};
Red.prototype.invm = function invm (a) {
var inv = a._invmp(this.m);
if (inv.negative !== 0) {
inv.negative = 0;
return this.imod(inv).redNeg();
} else {
return this.imod(inv);
}
};
Red.prototype.pow = function pow (a, num) {
if (num.isZero()) return new BN(1).toRed(this);
if (num.cmpn(1) === 0) return a.clone();
var windowSize = 4;
var wnd = new Array(1 << windowSize);
wnd[0] = new BN(1).toRed(this);
wnd[1] = a;
for (var i = 2; i < wnd.length; i++) {
wnd[i] = this.mul(wnd[i - 1], a);
}
var res = wnd[0];
var current = 0;
var currentLen = 0;
var start = num.bitLength() % 26;
if (start === 0) {
start = 26;
}
for (i = num.length - 1; i >= 0; i--) {
var word = num.words[i];
for (var j = start - 1; j >= 0; j--) {
var bit = (word >> j) & 1;
if (res !== wnd[0]) {
res = this.sqr(res);
}
if (bit === 0 && current === 0) {
currentLen = 0;
continue;
}
current <<= 1;
current |= bit;
currentLen++;
if (currentLen !== windowSize && (i !== 0 || j !== 0)) continue;
res = this.mul(res, wnd[current]);
currentLen = 0;
current = 0;
}
start = 26;
}
return res;
};
Red.prototype.convertTo = function convertTo (num) {
var r = num.umod(this.m);
return r === num ? r.clone() : r;
};
Red.prototype.convertFrom = function convertFrom (num) {
var res = num.clone();
res.red = null;
return res;
};
//
// Montgomery method engine
//
BN.mont = function mont (num) {
return new Mont(num);
};
function Mont (m) {
Red.call(this, m);
this.shift = this.m.bitLength();
if (this.shift % 26 !== 0) {
this.shift += 26 - (this.shift % 26);
}
this.r = new BN(1).iushln(this.shift);
this.r2 = this.imod(this.r.sqr());
this.rinv = this.r._invmp(this.m);
this.minv = this.rinv.mul(this.r).isubn(1).div(this.m);
this.minv = this.minv.umod(this.r);
this.minv = this.r.sub(this.minv);
}
inherits(Mont, Red);
Mont.prototype.convertTo = function convertTo (num) {
return this.imod(num.ushln(this.shift));
};
Mont.prototype.convertFrom = function convertFrom (num) {
var r = this.imod(num.mul(this.rinv));
r.red = null;
return r;
};
Mont.prototype.imul = function imul (a, b) {
if (a.isZero() || b.isZero()) {
a.words[0] = 0;
a.length = 1;
return a;
}
var t = a.imul(b);
var c = t.maskn(this.shift).mul(this.minv).imaskn(this.shift).mul(this.m);
var u = t.isub(c).iushrn(this.shift);
var res = u;
if (u.cmp(this.m) >= 0) {
res = u.isub(this.m);
} else if (u.cmpn(0) < 0) {
res = u.iadd(this.m);
}
return res._forceRed(this);
};
Mont.prototype.mul = function mul (a, b) {
if (a.isZero() || b.isZero()) return new BN(0)._forceRed(this);
var t = a.mul(b);
var c = t.maskn(this.shift).mul(this.minv).imaskn(this.shift).mul(this.m);
var u = t.isub(c).iushrn(this.shift);
var res = u;
if (u.cmp(this.m) >= 0) {
res = u.isub(this.m);
} else if (u.cmpn(0) < 0) {
res = u.iadd(this.m);
}
return res._forceRed(this);
};
Mont.prototype.invm = function invm (a) {
// (AR)^-1 * R^2 = (A^-1 * R^-1) * R^2 = A^-1 * R
var res = this.imod(a._invmp(this.m).mul(this.r2));
return res._forceRed(this);
};
})(typeof module === 'undefined' || module, this);
},{"buffer":2}],34:[function(_glvis_,module,exports){
'use strict'
module.exports = boundary
function boundary (cells) {
var i, j, k
var n = cells.length
var sz = 0
for (i = 0; i < n; ++i) {
sz += cells[i].length
}
var result = new Array(sz)
var ptr = 0
for (i = 0; i < n; ++i) {
var c = cells[i]
var d = c.length
for (j = 0; j < d; ++j) {
var b = result[ptr++] = new Array(d - 1)
var p = 0
for (k = 0; k < d; ++k) {
if (k === j) {
continue
}
b[p++] = c[k]
}
if (j & 1) {
var tmp = b[1]
b[1] = b[0]
b[0] = tmp
}
}
}
return result
}
},{}],35:[function(_glvis_,module,exports){
'use strict'
module.exports = boxIntersectWrapper
var pool = _glvis_('typedarray-pool')
var sweep = _glvis_('./lib/sweep')
var boxIntersectIter = _glvis_('./lib/intersect')
function boxEmpty(d, box) {
for(var j=0; j>>1
if(d <= 0) {
return
}
var retval
//Convert red boxes
var redList = pool.mallocDouble(2*d*n)
var redIds = pool.mallocInt32(n)
n = convertBoxes(red, d, redList, redIds)
if(n > 0) {
if(d === 1 && full) {
//Special case: 1d complete
sweep.init(n)
retval = sweep.sweepComplete(
d, visit,
0, n, redList, redIds,
0, n, redList, redIds)
} else {
//Convert blue boxes
var blueList = pool.mallocDouble(2*d*m)
var blueIds = pool.mallocInt32(m)
m = convertBoxes(blue, d, blueList, blueIds)
if(m > 0) {
sweep.init(n+m)
if(d === 1) {
//Special case: 1d bipartite
retval = sweep.sweepBipartite(
d, visit,
0, n, redList, redIds,
0, m, blueList, blueIds)
} else {
//General case: d>1
retval = boxIntersectIter(
d, visit, full,
n, redList, redIds,
m, blueList, blueIds)
}
pool.free(blueList)
pool.free(blueIds)
}
}
pool.free(redList)
pool.free(redIds)
}
return retval
}
var RESULT
function appendItem(i,j) {
RESULT.push([i,j])
}
function intersectFullArray(x) {
RESULT = []
boxIntersect(x, x, appendItem, true)
return RESULT
}
function intersectBipartiteArray(x, y) {
RESULT = []
boxIntersect(x, y, appendItem, false)
return RESULT
}
//User-friendly wrapper, handle full input and no-visitor cases
function boxIntersectWrapper(arg0, arg1, arg2) {
switch(arguments.length) {
case 1:
return intersectFullArray(arg0)
case 2:
if(typeof arg1 === 'function') {
return boxIntersect(arg0, arg0, arg1, true)
} else {
return intersectBipartiteArray(arg0, arg1)
}
case 3:
return boxIntersect(arg0, arg1, arg2, false)
default:
throw new Error('box-intersect: Invalid arguments')
}
}
},{"./lib/intersect":37,"./lib/sweep":41,"typedarray-pool":308}],36:[function(_glvis_,module,exports){
'use strict'
function full() {
function bruteForceRedFull(d, ax, vv, rs, re, rb, ri, bs, be, bb, bi) {
var es = 2 * d
for (var i = rs, rp = es * rs; i < re; ++i, rp += es) {
var x0 = rb[ax + rp], x1 = rb[ax + rp + d], xi = ri[i]
Q: for (var j = bs, bp = es * bs; j < be; ++j, bp += es) {
var y0 = bb[ax + bp], y1 = bb[ax + bp + d], yi = bi[j]
if (y1 < x0 || x1 < y0) continue
for (var k = ax + 1; k < d; ++k) {
var r0 = rb[k + rp], r1 = rb[k + d + rp], b0 = bb[k + bp], b1 = bb[k + d + bp]
if (r1 < b0 || b1 < r0) continue Q
}
var rv = vv(xi, yi)
if (rv !== void 0) return rv
}
}
}
function bruteForceBlueFull(d, ax, vv, rs, re, rb, ri, bs, be, bb, bi) {
var es = 2 * d
for (var j = bs, bp = es * bs; j < be; ++j, bp += es) {
var y0 = bb[ax + bp], y1 = bb[ax + bp + d], yi = bi[j]
Q: for (var i = rs, rp = es * rs; i < re; ++i, rp += es) {
var x0 = rb[ax + rp], x1 = rb[ax + rp + d], xi = ri[i]
if (y1 < x0 || x1 < y0) continue
for (var k = ax + 1; k < d; ++k) {
var r0 = rb[k + rp], r1 = rb[k + d + rp], b0 = bb[k + bp], b1 = bb[k + d + bp]
if (r1 < b0 || b1 < r0) continue Q
}
var rv = vv(xi, yi)
if (rv !== void 0) return rv
}
}
}
function bruteForceFull(d, ax, vv, rs, re, rb, ri, bs, be, bb, bi) {
if (re - rs > be - bs) {
return bruteForceRedFull(d, ax, vv, rs, re, rb, ri, bs, be, bb, bi)
}
else {
return bruteForceBlueFull(d, ax, vv, rs, re, rb, ri, bs, be, bb, bi)
}
}
return bruteForceFull
}
function partial() {
function bruteForceRedFlip(d, ax, vv, rs, re, rb, ri, bs, be, bb, bi) {
var es = 2 * d
for (var i = rs, rp = es * rs; i < re; ++i, rp += es) {
var x0 = rb[ax + rp], x1 = rb[ax + rp + d], xi = ri[i]
Q: for (var j = bs, bp = es * bs; j < be; ++j, bp += es) {
var y0 = bb[ax + bp], yi = bi[j]
if (y0 <= x0 || x1 < y0) continue
for (var k = ax + 1; k < d; ++k) {
var r0 = rb[k + rp], r1 = rb[k + d + rp], b0 = bb[k + bp], b1 = bb[k + d + bp]
if (r1 < b0 || b1 < r0) continue Q
}
var rv = vv(yi, xi)
if (rv !== void 0) return rv
}
}
}
function bruteForceRed(d, ax, vv, rs, re, rb, ri, bs, be, bb, bi) {
var es = 2 * d
for (var i = rs, rp = es * rs; i < re; ++i, rp += es) {
var x0 = rb[ax + rp], x1 = rb[ax + rp + d], xi = ri[i]
Q: for (var j = bs, bp = es * bs; j < be; ++j, bp += es) {
var y0 = bb[ax + bp], yi = bi[j]
if (y0 < x0 || x1 < y0) continue
for (var k = ax + 1; k < d; ++k) {
var r0 = rb[k + rp], r1 = rb[k + d + rp], b0 = bb[k + bp], b1 = bb[k + d + bp]
if (r1 < b0 || b1 < r0) continue Q
}
var rv = vv(xi, yi)
if (rv !== void 0) return rv
}
}
}
function bruteForceBlueFlip(d, ax, vv, rs, re, rb, ri, bs, be, bb, bi) {
var es = 2 * d
for (var j = bs, bp = es * bs; j < be; ++j, bp += es) {
var y0 = bb[ax + bp], yi = bi[j]
Q: for (var i = rs, rp = es * rs; i < re; ++i, rp += es) {
var x0 = rb[ax + rp], x1 = rb[ax + rp + d], xi = ri[i]
if (y0 <= x0 || x1 < y0) continue
for (var k = ax + 1; k < d; ++k) {
var r0 = rb[k + rp], r1 = rb[k + d + rp], b0 = bb[k + bp], b1 = bb[k + d + bp]
if (r1 < b0 || b1 < r0) continue Q
}
var rv = vv(yi, xi)
if (rv !== void 0) return rv
}
}
}
function bruteForceBlue(d, ax, vv, rs, re, rb, ri, bs, be, bb, bi) {
var es = 2 * d
for (var j = bs, bp = es * bs; j < be; ++j, bp += es) {
var y0 = bb[ax + bp], yi = bi[j]
Q: for (var i = rs, rp = es * rs; i < re; ++i, rp += es) {
var x0 = rb[ax + rp], x1 = rb[ax + rp + d], xi = ri[i]
if (y0 < x0 || x1 < y0) continue
for (var k = ax + 1; k < d; ++k) {
var r0 = rb[k + rp], r1 = rb[k + d + rp], b0 = bb[k + bp], b1 = bb[k + d + bp]
if (r1 < b0 || b1 < r0) continue Q
}
var rv = vv(xi, yi)
if (rv !== void 0) return rv
}
}
}
function bruteForcePartial(d, ax, vv, fp, rs, re, rb, ri, bs, be, bb, bi) {
if (re - rs > be - bs) {
if (fp) {
return bruteForceRedFlip(d, ax, vv, rs, re, rb, ri, bs, be, bb, bi)
}
else {
return bruteForceRed(d, ax, vv, rs, re, rb, ri, bs, be, bb, bi)
}
}
else {
if (fp) {
return bruteForceBlueFlip(d, ax, vv, rs, re, rb, ri, bs, be, bb, bi)
}
else {
return bruteForceBlue(d, ax, vv, rs, re, rb, ri, bs, be, bb, bi)
}
}
}
return bruteForcePartial
}
function bruteForcePlanner(isFull) {
return isFull ? full() : partial()
}
exports.partial = bruteForcePlanner(false)
exports.full = bruteForcePlanner(true)
},{}],37:[function(_glvis_,module,exports){
'use strict'
module.exports = boxIntersectIter
var pool = _glvis_('typedarray-pool')
var bits = _glvis_('bit-twiddle')
var bruteForce = _glvis_('./brute')
var bruteForcePartial = bruteForce.partial
var bruteForceFull = bruteForce.full
var sweep = _glvis_('./sweep')
var findMedian = _glvis_('./median')
var genPartition = _glvis_('./partition')
//Twiddle parameters
var BRUTE_FORCE_CUTOFF = 128 //Cut off for brute force search
var SCAN_CUTOFF = (1<<22) //Cut off for two way scan
var SCAN_COMPLETE_CUTOFF = (1<<22)
//Partition functions
var partitionInteriorContainsInterval = genPartition(
'!(lo>=p0)&&!(p1>=hi)')
var partitionStartEqual = genPartition(
'lo===p0')
var partitionStartLessThan = genPartition(
'lo 0) {
top -= 1
var iptr = top * IFRAME_SIZE
var axis = BOX_ISTACK[iptr]
var redStart = BOX_ISTACK[iptr+1]
var redEnd = BOX_ISTACK[iptr+2]
var blueStart = BOX_ISTACK[iptr+3]
var blueEnd = BOX_ISTACK[iptr+4]
var state = BOX_ISTACK[iptr+5]
var dptr = top * DFRAME_SIZE
var lo = BOX_DSTACK[dptr]
var hi = BOX_DSTACK[dptr+1]
//Unpack state info
var flip = (state & 1)
var full = !!(state & 16)
//Unpack indices
var red = xBoxes
var redIndex = xIndex
var blue = yBoxes
var blueIndex = yIndex
if(flip) {
red = yBoxes
redIndex = yIndex
blue = xBoxes
blueIndex = xIndex
}
if(state & 2) {
redEnd = partitionStartLessThan(
d, axis,
redStart, redEnd, red, redIndex,
hi)
if(redStart >= redEnd) {
continue
}
}
if(state & 4) {
redStart = partitionEndLessThanEqual(
d, axis,
redStart, redEnd, red, redIndex,
lo)
if(redStart >= redEnd) {
continue
}
}
var redCount = redEnd - redStart
var blueCount = blueEnd - blueStart
if(full) {
if(d * redCount * (redCount + blueCount) < SCAN_COMPLETE_CUTOFF) {
retval = sweep.scanComplete(
d, axis, visit,
redStart, redEnd, red, redIndex,
blueStart, blueEnd, blue, blueIndex)
if(retval !== void 0) {
return retval
}
continue
}
} else {
if(d * Math.min(redCount, blueCount) < BRUTE_FORCE_CUTOFF) {
//If input small, then use brute force
retval = bruteForcePartial(
d, axis, visit, flip,
redStart, redEnd, red, redIndex,
blueStart, blueEnd, blue, blueIndex)
if(retval !== void 0) {
return retval
}
continue
} else if(d * redCount * blueCount < SCAN_CUTOFF) {
//If input medium sized, then use sweep and prune
retval = sweep.scanBipartite(
d, axis, visit, flip,
redStart, redEnd, red, redIndex,
blueStart, blueEnd, blue, blueIndex)
if(retval !== void 0) {
return retval
}
continue
}
}
//First, find all red intervals whose interior contains (lo,hi)
var red0 = partitionInteriorContainsInterval(
d, axis,
redStart, redEnd, red, redIndex,
lo, hi)
//Lower dimensional case
if(redStart < red0) {
if(d * (red0 - redStart) < BRUTE_FORCE_CUTOFF) {
//Special case for small inputs: use brute force
retval = bruteForceFull(
d, axis+1, visit,
redStart, red0, red, redIndex,
blueStart, blueEnd, blue, blueIndex)
if(retval !== void 0) {
return retval
}
} else if(axis === d-2) {
if(flip) {
retval = sweep.sweepBipartite(
d, visit,
blueStart, blueEnd, blue, blueIndex,
redStart, red0, red, redIndex)
} else {
retval = sweep.sweepBipartite(
d, visit,
redStart, red0, red, redIndex,
blueStart, blueEnd, blue, blueIndex)
}
if(retval !== void 0) {
return retval
}
} else {
iterPush(top++,
axis+1,
redStart, red0,
blueStart, blueEnd,
flip,
-Infinity, Infinity)
iterPush(top++,
axis+1,
blueStart, blueEnd,
redStart, red0,
flip^1,
-Infinity, Infinity)
}
}
//Divide and conquer phase
if(red0 < redEnd) {
//Cut blue into 3 parts:
//
// Points < mid point
// Points = mid point
// Points > mid point
//
var blue0 = findMedian(
d, axis,
blueStart, blueEnd, blue, blueIndex)
var mid = blue[elemSize * blue0 + axis]
var blue1 = partitionStartEqual(
d, axis,
blue0, blueEnd, blue, blueIndex,
mid)
//Right case
if(blue1 < blueEnd) {
iterPush(top++,
axis,
red0, redEnd,
blue1, blueEnd,
(flip|4) + (full ? 16 : 0),
mid, hi)
}
//Left case
if(blueStart < blue0) {
iterPush(top++,
axis,
red0, redEnd,
blueStart, blue0,
(flip|2) + (full ? 16 : 0),
lo, mid)
}
//Center case (the hard part)
if(blue0 + 1 === blue1) {
//Optimization: Range with exactly 1 point, use a brute force scan
if(full) {
retval = onePointFull(
d, axis, visit,
red0, redEnd, red, redIndex,
blue0, blue, blueIndex[blue0])
} else {
retval = onePointPartial(
d, axis, visit, flip,
red0, redEnd, red, redIndex,
blue0, blue, blueIndex[blue0])
}
if(retval !== void 0) {
return retval
}
} else if(blue0 < blue1) {
var red1
if(full) {
//If full intersection, need to handle special case
red1 = partitionContainsPoint(
d, axis,
red0, redEnd, red, redIndex,
mid)
if(red0 < red1) {
var redX = partitionStartEqual(
d, axis,
red0, red1, red, redIndex,
mid)
if(axis === d-2) {
//Degenerate sweep intersection:
// [red0, redX] with [blue0, blue1]
if(red0 < redX) {
retval = sweep.sweepComplete(
d, visit,
red0, redX, red, redIndex,
blue0, blue1, blue, blueIndex)
if(retval !== void 0) {
return retval
}
}
//Normal sweep intersection:
// [redX, red1] with [blue0, blue1]
if(redX < red1) {
retval = sweep.sweepBipartite(
d, visit,
redX, red1, red, redIndex,
blue0, blue1, blue, blueIndex)
if(retval !== void 0) {
return retval
}
}
} else {
if(red0 < redX) {
iterPush(top++,
axis+1,
red0, redX,
blue0, blue1,
16,
-Infinity, Infinity)
}
if(redX < red1) {
iterPush(top++,
axis+1,
redX, red1,
blue0, blue1,
0,
-Infinity, Infinity)
iterPush(top++,
axis+1,
blue0, blue1,
redX, red1,
1,
-Infinity, Infinity)
}
}
}
} else {
if(flip) {
red1 = partitionContainsPointProper(
d, axis,
red0, redEnd, red, redIndex,
mid)
} else {
red1 = partitionContainsPoint(
d, axis,
red0, redEnd, red, redIndex,
mid)
}
if(red0 < red1) {
if(axis === d-2) {
if(flip) {
retval = sweep.sweepBipartite(
d, visit,
blue0, blue1, blue, blueIndex,
red0, red1, red, redIndex)
} else {
retval = sweep.sweepBipartite(
d, visit,
red0, red1, red, redIndex,
blue0, blue1, blue, blueIndex)
}
} else {
iterPush(top++,
axis+1,
red0, red1,
blue0, blue1,
flip,
-Infinity, Infinity)
iterPush(top++,
axis+1,
blue0, blue1,
red0, red1,
flip^1,
-Infinity, Infinity)
}
}
}
}
}
}
}
},{"./brute":36,"./median":38,"./partition":39,"./sweep":41,"bit-twiddle":32,"typedarray-pool":308}],38:[function(_glvis_,module,exports){
'use strict'
module.exports = findMedian
var genPartition = _glvis_('./partition')
var partitionStartLessThan = genPartition('lostart && boxes[ptr+axis] > x;
--j, ptr-=elemSize) {
//Swap
var aPtr = ptr
var bPtr = ptr+elemSize
for(var k=0; k>> 1)
var elemSize = 2*d
var pivot = mid
var value = boxes[elemSize*mid+axis]
while(lo < hi) {
if(hi - lo < PARTITION_THRESHOLD) {
insertionSort(d, axis, lo, hi, boxes, ids)
value = boxes[elemSize*mid+axis]
break
}
//Select pivot using median-of-3
var count = hi - lo
var pivot0 = (Math.random()*count+lo)|0
var value0 = boxes[elemSize*pivot0 + axis]
var pivot1 = (Math.random()*count+lo)|0
var value1 = boxes[elemSize*pivot1 + axis]
var pivot2 = (Math.random()*count+lo)|0
var value2 = boxes[elemSize*pivot2 + axis]
if(value0 <= value1) {
if(value2 >= value1) {
pivot = pivot1
value = value1
} else if(value0 >= value2) {
pivot = pivot0
value = value0
} else {
pivot = pivot2
value = value2
}
} else {
if(value1 >= value2) {
pivot = pivot1
value = value1
} else if(value2 >= value0) {
pivot = pivot0
value = value0
} else {
pivot = pivot2
value = value2
}
}
//Swap pivot to end of array
var aPtr = elemSize * (hi-1)
var bPtr = elemSize * pivot
for(var i=0; i=p0)&&!(p1>=hi)': lo_lessThan_p0_and_p1_lessThan_hi
}
function genPartition(predicate) {
return P2F[predicate]
}
// lo===p0
function lo_equal_p0(a, b, c, d, e, f, p0) {
for (var j = 2 * a, k = j * c, l = k, m = c, n = b, o = a + b, p = c; d > p; ++p, k += j) {
var lo = e[k + n];
if (lo === p0) if (m === p) m += 1, l += j; else {
for (var s = 0; j > s; ++s) {
var t = e[k + s]; e[k + s] = e[l], e[l++] = t
} var u = f[p]; f[p] = f[m], f[m++] = u
}
}
return m
}
// lo p; ++p, k += j) {
var lo = e[k + n];
if (lo < p0) if (m === p) m += 1, l += j; else {
for (var s = 0; j > s; ++s) {
var t = e[k + s]; e[k + s] = e[l], e[l++] = t
} var u = f[p]; f[p] = f[m], f[m++] = u
}
}
return m
}
// lo<=p0
function lo_lessOrEqual_p0(a, b, c, d, e, f, p0) {
for (var j = 2 * a, k = j * c, l = k, m = c, n = b, o = a + b, p = c; d > p; ++p, k += j) {
var hi = e[k + o];
if (hi <= p0) if (m === p) m += 1, l += j; else {
for (var s = 0; j > s; ++s) {
var t = e[k + s]; e[k + s] = e[l], e[l++] = t
}
var u = f[p]; f[p] = f[m], f[m++] = u
}
} return m
}
// hi<=p0
function hi_lessOrEqual_p0(a, b, c, d, e, f, p0) {
for (var j = 2 * a, k = j * c, l = k, m = c, n = b, o = a + b, p = c; d > p; ++p, k += j) {
var hi = e[k + o];
if (hi <= p0) if (m === p) m += 1, l += j; else {
for (var s = 0; j > s; ++s) {
var t = e[k + s]; e[k + s] = e[l], e[l++] = t
}
var u = f[p]; f[p] = f[m], f[m++] = u
}
}
return m
}
// lo<=p0&&p0<=hi
function lo_lassOrEqual_p0_and_p0_lessOrEqual_hi(a, b, c, d, e, f, p0) {
for (var j = 2 * a, k = j * c, l = k, m = c, n = b, o = a + b, p = c; d > p; ++p, k += j) {
var lo = e[k + n], hi = e[k + o];
if (lo <= p0 && p0 <= hi) if (m === p) m += 1, l += j; else {
for (var s = 0; j > s; ++s) {
var t = e[k + s]; e[k + s] = e[l], e[l++] = t
}
var u = f[p]; f[p] = f[m], f[m++] = u
}
}
return m
}
// lo p; ++p, k += j) {
var lo = e[k + n], hi = e[k + o];
if (lo < p0 && p0 <= hi) if (m === p) m += 1, l += j; else {
for (var s = 0; j > s; ++s) {
var t = e[k + s]; e[k + s] = e[l], e[l++] = t
}
var u = f[p]; f[p] = f[m], f[m++] = u
}
}
return m
}
// !(lo>=p0)&&!(p1>=hi)
function lo_lessThan_p0_and_p1_lessThan_hi(a, b, c, d, e, f, p0, p1) {
for (var j = 2 * a, k = j * c, l = k, m = c, n = b, o = a + b, p = c; d > p; ++p, k += j) {
var lo = e[k + n], hi = e[k + o];
if (!(lo >= p0) && !(p1 >= hi)) if (m === p) m += 1, l += j; else {
for (var s = 0; j > s; ++s) {
var t = e[k + s]; e[k + s] = e[l], e[l++] = t
}
var u = f[p]; f[p] = f[m], f[m++] = u
}
}
return m
}
},{}],40:[function(_glvis_,module,exports){
'use strict';
//This code is extracted from ndarray-sort
//It is inlined here as a temporary workaround
module.exports = wrapper;
var INSERT_SORT_CUTOFF = 32
function wrapper(data, n0) {
if (n0 <= 4*INSERT_SORT_CUTOFF) {
insertionSort(0, n0 - 1, data);
} else {
quickSort(0, n0 - 1, data);
}
}
function insertionSort(left, right, data) {
var ptr = 2*(left+1)
for(var i=left+1; i<=right; ++i) {
var a = data[ptr++]
var b = data[ptr++]
var j = i
var jptr = ptr-2
while(j-- > left) {
var x = data[jptr-2]
var y = data[jptr-1]
if(x < a) {
break
} else if(x === a && y < b) {
break
}
data[jptr] = x
data[jptr+1] = y
jptr -= 2
}
data[jptr] = a
data[jptr+1] = b
}
}
function swap(i, j, data) {
i *= 2
j *= 2
var x = data[i]
var y = data[i+1]
data[i] = data[j]
data[i+1] = data[j+1]
data[j] = x
data[j+1] = y
}
function move(i, j, data) {
i *= 2
j *= 2
data[i] = data[j]
data[i+1] = data[j+1]
}
function rotate(i, j, k, data) {
i *= 2
j *= 2
k *= 2
var x = data[i]
var y = data[i+1]
data[i] = data[j]
data[i+1] = data[j+1]
data[j] = data[k]
data[j+1] = data[k+1]
data[k] = x
data[k+1] = y
}
function shufflePivot(i, j, px, py, data) {
i *= 2
j *= 2
data[i] = data[j]
data[j] = px
data[i+1] = data[j+1]
data[j+1] = py
}
function compare(i, j, data) {
i *= 2
j *= 2
var x = data[i],
y = data[j]
if(x < y) {
return false
} else if(x === y) {
return data[i+1] > data[j+1]
}
return true
}
function comparePivot(i, y, b, data) {
i *= 2
var x = data[i]
if(x < y) {
return true
} else if(x === y) {
return data[i+1] < b
}
return false
}
function quickSort(left, right, data) {
var sixth = (right - left + 1) / 6 | 0,
index1 = left + sixth,
index5 = right - sixth,
index3 = left + right >> 1,
index2 = index3 - sixth,
index4 = index3 + sixth,
el1 = index1,
el2 = index2,
el3 = index3,
el4 = index4,
el5 = index5,
less = left + 1,
great = right - 1,
tmp = 0
if(compare(el1, el2, data)) {
tmp = el1
el1 = el2
el2 = tmp
}
if(compare(el4, el5, data)) {
tmp = el4
el4 = el5
el5 = tmp
}
if(compare(el1, el3, data)) {
tmp = el1
el1 = el3
el3 = tmp
}
if(compare(el2, el3, data)) {
tmp = el2
el2 = el3
el3 = tmp
}
if(compare(el1, el4, data)) {
tmp = el1
el1 = el4
el4 = tmp
}
if(compare(el3, el4, data)) {
tmp = el3
el3 = el4
el4 = tmp
}
if(compare(el2, el5, data)) {
tmp = el2
el2 = el5
el5 = tmp
}
if(compare(el2, el3, data)) {
tmp = el2
el2 = el3
el3 = tmp
}
if(compare(el4, el5, data)) {
tmp = el4
el4 = el5
el5 = tmp
}
var pivot1X = data[2*el2]
var pivot1Y = data[2*el2+1]
var pivot2X = data[2*el4]
var pivot2Y = data[2*el4+1]
var ptr0 = 2 * el1;
var ptr2 = 2 * el3;
var ptr4 = 2 * el5;
var ptr5 = 2 * index1;
var ptr6 = 2 * index3;
var ptr7 = 2 * index5;
for (var i1 = 0; i1 < 2; ++i1) {
var x = data[ptr0+i1];
var y = data[ptr2+i1];
var z = data[ptr4+i1];
data[ptr5+i1] = x;
data[ptr6+i1] = y;
data[ptr7+i1] = z;
}
move(index2, left, data)
move(index4, right, data)
for (var k = less; k <= great; ++k) {
if (comparePivot(k, pivot1X, pivot1Y, data)) {
if (k !== less) {
swap(k, less, data)
}
++less;
} else {
if (!comparePivot(k, pivot2X, pivot2Y, data)) {
while (true) {
if (!comparePivot(great, pivot2X, pivot2Y, data)) {
if (--great < k) {
break;
}
continue;
} else {
if (comparePivot(great, pivot1X, pivot1Y, data)) {
rotate(k, less, great, data)
++less;
--great;
} else {
swap(k, great, data)
--great;
}
break;
}
}
}
}
}
shufflePivot(left, less-1, pivot1X, pivot1Y, data)
shufflePivot(right, great+1, pivot2X, pivot2Y, data)
if (less - 2 - left <= INSERT_SORT_CUTOFF) {
insertionSort(left, less - 2, data);
} else {
quickSort(left, less - 2, data);
}
if (right - (great + 2) <= INSERT_SORT_CUTOFF) {
insertionSort(great + 2, right, data);
} else {
quickSort(great + 2, right, data);
}
if (great - less <= INSERT_SORT_CUTOFF) {
insertionSort(less, great, data);
} else {
quickSort(less, great, data);
}
}
},{}],41:[function(_glvis_,module,exports){
'use strict'
module.exports = {
init: sqInit,
sweepBipartite: sweepBipartite,
sweepComplete: sweepComplete,
scanBipartite: scanBipartite,
scanComplete: scanComplete
}
var pool = _glvis_('typedarray-pool')
var bits = _glvis_('bit-twiddle')
var isort = _glvis_('./sort')
//Flag for blue
var BLUE_FLAG = (1<<28)
//1D sweep event queue stuff (use pool to save space)
var INIT_CAPACITY = 1024
var RED_SWEEP_QUEUE = pool.mallocInt32(INIT_CAPACITY)
var RED_SWEEP_INDEX = pool.mallocInt32(INIT_CAPACITY)
var BLUE_SWEEP_QUEUE = pool.mallocInt32(INIT_CAPACITY)
var BLUE_SWEEP_INDEX = pool.mallocInt32(INIT_CAPACITY)
var COMMON_SWEEP_QUEUE = pool.mallocInt32(INIT_CAPACITY)
var COMMON_SWEEP_INDEX = pool.mallocInt32(INIT_CAPACITY)
var SWEEP_EVENTS = pool.mallocDouble(INIT_CAPACITY * 8)
//Reserves memory for the 1D sweep data structures
function sqInit(count) {
var rcount = bits.nextPow2(count)
if(RED_SWEEP_QUEUE.length < rcount) {
pool.free(RED_SWEEP_QUEUE)
RED_SWEEP_QUEUE = pool.mallocInt32(rcount)
}
if(RED_SWEEP_INDEX.length < rcount) {
pool.free(RED_SWEEP_INDEX)
RED_SWEEP_INDEX = pool.mallocInt32(rcount)
}
if(BLUE_SWEEP_QUEUE.length < rcount) {
pool.free(BLUE_SWEEP_QUEUE)
BLUE_SWEEP_QUEUE = pool.mallocInt32(rcount)
}
if(BLUE_SWEEP_INDEX.length < rcount) {
pool.free(BLUE_SWEEP_INDEX)
BLUE_SWEEP_INDEX = pool.mallocInt32(rcount)
}
if(COMMON_SWEEP_QUEUE.length < rcount) {
pool.free(COMMON_SWEEP_QUEUE)
COMMON_SWEEP_QUEUE = pool.mallocInt32(rcount)
}
if(COMMON_SWEEP_INDEX.length < rcount) {
pool.free(COMMON_SWEEP_INDEX)
COMMON_SWEEP_INDEX = pool.mallocInt32(rcount)
}
var eventLength = 8 * rcount
if(SWEEP_EVENTS.length < eventLength) {
pool.free(SWEEP_EVENTS)
SWEEP_EVENTS = pool.mallocDouble(eventLength)
}
}
//Remove an item from the active queue in O(1)
function sqPop(queue, index, count, item) {
var idx = index[item]
var top = queue[count-1]
queue[idx] = top
index[top] = idx
}
//Insert an item into the active queue in O(1)
function sqPush(queue, index, count, item) {
queue[count] = item
index[item] = count
}
//Recursion base case: use 1D sweep algorithm
function sweepBipartite(
d, visit,
redStart, redEnd, red, redIndex,
blueStart, blueEnd, blue, blueIndex) {
//store events as pairs [coordinate, idx]
//
// red create: -(idx+1)
// red destroy: idx
// blue create: -(idx+BLUE_FLAG)
// blue destroy: idx+BLUE_FLAG
//
var ptr = 0
var elemSize = 2*d
var istart = d-1
var iend = elemSize-1
for(var i=redStart; iright
var n = ptr >>> 1
isort(SWEEP_EVENTS, n)
var redActive = 0
var blueActive = 0
for(var i=0; i= BLUE_FLAG) {
//blue destroy event
e = (e-BLUE_FLAG)|0
sqPop(BLUE_SWEEP_QUEUE, BLUE_SWEEP_INDEX, blueActive--, e)
} else if(e >= 0) {
//red destroy event
sqPop(RED_SWEEP_QUEUE, RED_SWEEP_INDEX, redActive--, e)
} else if(e <= -BLUE_FLAG) {
//blue create event
e = (-e-BLUE_FLAG)|0
for(var j=0; jright
var n = ptr >>> 1
isort(SWEEP_EVENTS, n)
var redActive = 0
var blueActive = 0
var commonActive = 0
for(var i=0; i>1) === (SWEEP_EVENTS[2*i+3]>>1)) {
color = 2
i += 1
}
if(e < 0) {
//Create event
var id = -(e>>1) - 1
//Intersect with common
for(var j=0; j>1) - 1
if(color === 0) {
//Red
sqPop(RED_SWEEP_QUEUE, RED_SWEEP_INDEX, redActive--, id)
} else if(color === 1) {
//Blue
sqPop(BLUE_SWEEP_QUEUE, BLUE_SWEEP_INDEX, blueActive--, id)
} else if(color === 2) {
//Both
sqPop(COMMON_SWEEP_QUEUE, COMMON_SWEEP_INDEX, commonActive--, id)
}
}
}
}
//Sweep and prune/scanline algorithm:
// Scan along axis, detect intersections
// Brute force all boxes along axis
function scanBipartite(
d, axis, visit, flip,
redStart, redEnd, red, redIndex,
blueStart, blueEnd, blue, blueIndex) {
var ptr = 0
var elemSize = 2*d
var istart = axis
var iend = axis+d
var redShift = 1
var blueShift = 1
if(flip) {
blueShift = BLUE_FLAG
} else {
redShift = BLUE_FLAG
}
for(var i=redStart; iright
var n = ptr >>> 1
isort(SWEEP_EVENTS, n)
var redActive = 0
for(var i=0; i= BLUE_FLAG) {
isRed = !flip
idx -= BLUE_FLAG
} else {
isRed = !!flip
idx -= 1
}
if(isRed) {
sqPush(RED_SWEEP_QUEUE, RED_SWEEP_INDEX, redActive++, idx)
} else {
var blueId = blueIndex[idx]
var bluePtr = elemSize * idx
var b0 = blue[bluePtr+axis+1]
var b1 = blue[bluePtr+axis+1+d]
red_loop:
for(var j=0; jright
var n = ptr >>> 1
isort(SWEEP_EVENTS, n)
var redActive = 0
for(var i=0; i= BLUE_FLAG) {
RED_SWEEP_QUEUE[redActive++] = idx - BLUE_FLAG
} else {
idx -= 1
var blueId = blueIndex[idx]
var bluePtr = elemSize * idx
var b0 = blue[bluePtr+axis+1]
var b1 = blue[bluePtr+axis+1+d]
red_loop:
for(var j=0; j